Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00bd028d8b | |||
| 273dc41559 | |||
| 61bc74148f | |||
| e181a1cfb1 | |||
| fdbe3a1f6a | |||
| 5b15ed6f15 | |||
| 57703af642 | |||
| 9dd4c5d871 | |||
| 4813cf6c18 | |||
| 83b76c6b37 | |||
| 4120234155 | |||
| 42b444ddf8 | |||
| acd832d8e5 | |||
| 75a726ebbe | |||
| b9fbab6024 | |||
| 06ed8716a1 | |||
| beddd3dcee | |||
| 4d02dfb899 | |||
| 80d6cb5c12 | |||
| 7422ab69ba | |||
| 60c8185bea | |||
| 9d3c7c0be8 | |||
| d6048fae52 | |||
| ddb7b0e872 | |||
| 4995dde705 | |||
| 63bfe4246f | |||
| 4654b360e0 | |||
| 7c5c19986a | |||
| 46edca0bd4 | |||
| b03a1f922d | |||
| 9616c90433 | |||
| ea49f0a265 | |||
| 2470c8b9b5 | |||
| 7d001965ec | |||
| 11b1089d83 | |||
| 52699d7598 | |||
| 87443dd624 | |||
| e0f2fa6705 | |||
| 7949b07213 | |||
| bd20fd0b1f | |||
| 57d3d6fd0f | |||
| 06eff652dd | |||
| 246decac4a | |||
| a14ae298ae | |||
| 43188051d3 | |||
| 8aa15fa467 | |||
| f77b037ef7 | |||
| 75dbc5d790 | |||
| e6bcd706ff | |||
| 9a077a0928 | |||
| 11fd42e187 | |||
| 6e499e5996 | |||
| f98cc73a1f | |||
| db9bf4d3fa | |||
| b30628529d | |||
| 3be615f74f | |||
| a8ccd2ec17 | |||
| 2d71caf50a | |||
| 4d08d62fc2 | |||
| 40e5595d65 | |||
| 6780d58a98 | |||
| ccbf06f179 | |||
| e6ecf71cc3 | |||
| c66312ee96 | |||
| 808802aeb4 | |||
| fb38f24e5c | |||
| 65f148a5ad | |||
| 2bcdc5d11e | |||
| 5ecb03ffe9 | |||
| 8505989dad | |||
| ed5e865db7 | |||
| ea3194e0c3 | |||
| 190bff4ffe | |||
| c509555d15 | |||
| f0d66fdd88 | |||
| 14847d01b7 | |||
| f4711902bd | |||
| d92d6877a4 | |||
| a746738d48 | |||
| ce676c2bb5 | |||
| a096f9d54e | |||
| d9b6823106 | |||
| 89bcb5649b | |||
| a74a99d81d | |||
| c09d3af92f | |||
| 76773c58d7 | |||
| fd90175e87 | |||
| 0eb859467e |
+1
-1
@@ -14,7 +14,7 @@ GEOIPUPDATE_LICENSE_KEY=xxx
|
||||
# List of space-separated database edition IDs. Edition IDs may
|
||||
# consist of letters, digits, and dashes. For example, GeoIP2-City
|
||||
# would download the GeoIP2 City database (GeoIP2-City).
|
||||
GEOIPUPDATE_EDITION_IDS=GeoLite2-Country
|
||||
GEOIPUPDATE_EDITION_IDS=GeoLite2-City
|
||||
# The number of hours between geoipupdate runs. If this is not set
|
||||
# or is set to 0, geoipupdate will run once and exit.
|
||||
GEOIPUPDATE_FREQUENCY=72
|
||||
|
||||
+10
-7
@@ -6,16 +6,19 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
### Added
|
||||
|
||||
- socks5: send status message for service ready, and network-requester error response
|
||||
- socks5: send status message for service ready, and network-requester error response in https://github.com/nymtech/nym/pull/2715
|
||||
|
||||
### Changed
|
||||
|
||||
- all-binaries: improved error logging ([#2686])
|
||||
- native client: bring shutdown logic up to the same level as socks5-client
|
||||
- nym-api, coconut-dkg contract: automatic, time-based dkg epoch state advancement ([#2670])
|
||||
|
||||
[#2686]: https://github.com/nymtech/nym/pull/2686
|
||||
[#2670]: https://github.com/nymtech/nym/pull/2670
|
||||
- all-binaries: improved error logging in https://github.com/nymtech/nym/pull/2686
|
||||
- native client: bring shutdown logic up to the same level as socks5-client in https://github.com/nymtech/nym/pull/2695
|
||||
- nym-api, coconut-dkg contract: automatic, time-based dkg epoch state advancement in https://github.com/nymtech/nym/pull/2670
|
||||
- DKG resharing unit test by @neacsu in https://github.com/nymtech/nym/pull/2668
|
||||
- Renaming validator-api to nym-api by @futurechimp in https://github.com/nymtech/nym/pull/1863
|
||||
- Modify wasm specific make targets by @neacsu in https://github.com/nymtech/nym/pull/2693
|
||||
- client: create websocket handler builder by @octol in https://github.com/nymtech/nym/pull/2700
|
||||
- Outfox and Lion by @durch in https://github.com/nymtech/nym/pull/2730
|
||||
- Feature/multi surb transmission lanes by @jstuczyn in https://github.com/nymtech/nym/pull/2723
|
||||
|
||||
## [v1.1.4] (2022-12-20)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "client-core"
|
||||
version = "1.1.4"
|
||||
version = "1.1.5"
|
||||
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.4"
|
||||
version = "1.1.5"
|
||||
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.4"
|
||||
version = "1.1.5"
|
||||
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,2 +1,2 @@
|
||||
# The path to the geoip database file
|
||||
GEOIP_DB_PATH=./geo_ip/GeoLite2-Country.mmdb
|
||||
GEOIP_DB_PATH=./geo_ip/GeoLite2-City.mmdb
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -40,7 +40,7 @@ It should be previously installed thanks to `geoipupdate` service.
|
||||
For example:
|
||||
|
||||
```shell
|
||||
GEOIP_DB_PATH=./geo_ip/GeoLite2-Country.mmdb cargo run
|
||||
GEOIP_DB_PATH=./geo_ip/GeoLite2-City.mmdb cargo run
|
||||
```
|
||||
|
||||
Note: explorer-api binary reads the provided `.env` file.
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::geo_ip::location::Location;
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
use rocket::response::status;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{Route, State};
|
||||
use rocket_okapi::okapi::openapi3::OpenApi;
|
||||
use rocket_okapi::settings::OpenApiSettings;
|
||||
|
||||
pub fn nym_terms_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![settings: terms]
|
||||
}
|
||||
|
||||
#[openapi(tag = "terms")]
|
||||
#[get("/")]
|
||||
pub(crate) async fn terms(
|
||||
_state: &State<ExplorerApiStateContext>,
|
||||
location: Location,
|
||||
) -> Result<Json<String>, status::Forbidden<String>> {
|
||||
if location.iso_alpha2 == "US" {
|
||||
return Err(status::Forbidden(Some("US government sucks".to_string())));
|
||||
}
|
||||
Ok(Json("Nym Terms & Conditions: Welcome".to_string()))
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub(crate) mod http;
|
||||
@@ -3,14 +3,14 @@
|
||||
|
||||
use isocountry::CountryCode;
|
||||
use log::warn;
|
||||
use maxminddb::{geoip2::Country, MaxMindDBError, Reader};
|
||||
use maxminddb::{geoip2::City, MaxMindDBError, Reader};
|
||||
use std::{
|
||||
net::{IpAddr, ToSocketAddrs},
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
const DEFAULT_DATABASE_PATH: &str = "./geo_ip/GeoLite2-Country.mmdb";
|
||||
const DEFAULT_DATABASE_PATH: &str = "./geo_ip/GeoLite2-City.mmdb";
|
||||
const FAKE_PORT: u16 = 1234;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -38,6 +38,8 @@ pub(crate) struct Location {
|
||||
pub(crate) iso_alpha3: String,
|
||||
/// English country short name (ISO 3166-1)
|
||||
pub(crate) name: String,
|
||||
pub(crate) latitude: Option<f64>,
|
||||
pub(crate) longitude: Option<f64>,
|
||||
}
|
||||
|
||||
impl GeoIp {
|
||||
@@ -86,7 +88,7 @@ impl GeoIp {
|
||||
error!("No registered GeoIP database");
|
||||
GeoIpError::InternalError
|
||||
})?
|
||||
.lookup::<Country>(ip);
|
||||
.lookup::<City>(ip);
|
||||
match &result {
|
||||
Ok(v) => Ok(Some(
|
||||
Location::try_from(v).map_err(|_| GeoIpError::InternalError)?,
|
||||
@@ -99,11 +101,11 @@ impl GeoIp {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&Country<'a>> for Location {
|
||||
impl<'a> TryFrom<&City<'a>> for Location {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(country: &Country) -> Result<Self, Self::Error> {
|
||||
let data = country.country.as_ref().ok_or_else(|| {
|
||||
fn try_from(city: &City) -> Result<Self, Self::Error> {
|
||||
let data = city.country.as_ref().ok_or_else(|| {
|
||||
warn!("No Country data found");
|
||||
"No Country data found"
|
||||
})?;
|
||||
@@ -119,10 +121,13 @@ impl<'a> TryFrom<&Country<'a>> for Location {
|
||||
warn!("{}", &message);
|
||||
message
|
||||
})?;
|
||||
|
||||
Ok(Location {
|
||||
iso_alpha2,
|
||||
iso_alpha3: String::from(iso_codes.alpha3()),
|
||||
name: String::from(iso_codes.name()),
|
||||
latitude: city.location.as_ref().and_then(|l| l.latitude),
|
||||
longitude: city.location.as_ref().and_then(|l| l.longitude),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use rocket::{Build, Request, Rocket};
|
||||
use rocket_cors::{AllowedHeaders, AllowedOrigins};
|
||||
use rocket_okapi::swagger_ui::make_swagger_ui;
|
||||
|
||||
use crate::buy_terms::http::nym_terms_make_default_routes;
|
||||
use crate::country_statistics::http::country_statistics_make_default_routes;
|
||||
use crate::gateways::http::gateways_make_default_routes;
|
||||
use crate::http::swagger::get_docs;
|
||||
@@ -57,7 +56,6 @@ fn configure_rocket(state: ExplorerApiStateContext) -> Rocket<Build> {
|
||||
"/overview" => overview_make_default_routes(&openapi_settings),
|
||||
"/ping" => ping_make_default_routes(&openapi_settings),
|
||||
"/validators" => validators_make_default_routes(&openapi_settings),
|
||||
"/terms" => nym_terms_make_default_routes(&openapi_settings),
|
||||
};
|
||||
|
||||
building_rocket
|
||||
|
||||
@@ -10,7 +10,6 @@ use logging::setup_logging;
|
||||
use network_defaults::setup_env;
|
||||
use task::TaskManager;
|
||||
|
||||
mod buy_terms;
|
||||
pub(crate) mod cache;
|
||||
mod client;
|
||||
pub(crate) mod commands;
|
||||
|
||||
@@ -36,6 +36,8 @@ pub(crate) struct Location {
|
||||
pub(crate) two_letter_iso_country_code: String,
|
||||
pub(crate) three_letter_iso_country_code: String,
|
||||
pub(crate) country_name: String,
|
||||
pub(crate) latitude: Option<f64>,
|
||||
pub(crate) longitude: Option<f64>,
|
||||
}
|
||||
|
||||
impl Location {
|
||||
@@ -44,6 +46,8 @@ impl Location {
|
||||
country_name: location.name,
|
||||
two_letter_iso_country_code: location.iso_alpha2,
|
||||
three_letter_iso_country_code: location.iso_alpha3,
|
||||
latitude: location.latitude,
|
||||
longitude: location.longitude,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
## UNRELEASED
|
||||
|
||||
## [nym-explorer-v1.0.1](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.1) (2023-01-10)
|
||||
|
||||
- Feat/2161 ne gate version by @gala1234 in https://github.com/nymtech/nym/pull/2743
|
||||
- fix(explorer): set gateway bond 6 decimals by @doums in https://github.com/nymtech/nym/pull/2741
|
||||
- Feat/2130 tables update rebase by @gala1234 in https://github.com/nymtech/nym/pull/2742
|
||||
- fix(explorer,explorer-api): mixnode location by @doums in https://github.com/nymtech/nym-api
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nym/network-explorer",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Tooltip } from '@nymproject/react/tooltip/Tooltip';
|
||||
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard';
|
||||
import { Box } from '@mui/system';
|
||||
import { cellStyles } from './Universal-DataGrid';
|
||||
import { currencyToString } from '../utils/currency';
|
||||
import { unymToNym } from '../utils/currency';
|
||||
import { GatewayEnrichedRowType } from './Gateways';
|
||||
import { MixnodeRowType } from './MixNodes';
|
||||
|
||||
@@ -38,7 +38,7 @@ function formatCellValues(val: string | number, field: string) {
|
||||
);
|
||||
}
|
||||
if (field === 'bond') {
|
||||
return currencyToString(val.toString());
|
||||
return unymToNym(val, 6);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export type GatewayRowType = {
|
||||
bond: number;
|
||||
host: string;
|
||||
location: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
export type GatewayEnrichedRowType = GatewayRowType & {
|
||||
@@ -26,6 +27,7 @@ export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowTy
|
||||
location: gw?.gateway?.location || '',
|
||||
bond: gw.pledge_amount.amount || 0,
|
||||
host: gw.gateway.host || '',
|
||||
version: gw.gateway.version || '',
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -40,6 +42,7 @@ export function gatewayEnrichedToGridRow(
|
||||
location: gateway?.gateway?.location || '',
|
||||
bond: gateway.pledge_amount.amount || 0,
|
||||
host: gateway.gateway.host || '',
|
||||
version: gateway.gateway.version || '',
|
||||
clientsPort: gateway.gateway.clients_port || 0,
|
||||
mixPort: gateway.gateway.mix_port || 0,
|
||||
routingScore: `${report.most_recent}%`,
|
||||
|
||||
@@ -4,6 +4,8 @@ import Select, { SelectChangeEvent } from '@mui/material/Select';
|
||||
import { Filters } from './Filters/Filters';
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
|
||||
const fieldsHeight = '42.25px';
|
||||
|
||||
type TableToolBarProps = {
|
||||
onChangeSearch: (arg: string) => void;
|
||||
onChangePageSize: (event: SelectChangeEvent<string>) => void;
|
||||
@@ -35,7 +37,7 @@ export const TableToolbar: React.FC<TableToolBarProps> = ({
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', flexDirection: isMobile ? 'column-reverse' : 'row', alignItems: 'middle' }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', height: fieldsHeight }}>
|
||||
{childrenBefore}
|
||||
<Select
|
||||
labelId="simple-select-label"
|
||||
@@ -65,6 +67,9 @@ export const TableToolbar: React.FC<TableToolBarProps> = ({
|
||||
sx={{
|
||||
width: isMobile ? '100%' : 200,
|
||||
marginBottom: isMobile ? 2 : 0,
|
||||
'& > :not(style)': {
|
||||
height: fieldsHeight,
|
||||
},
|
||||
}}
|
||||
value={searchTerm}
|
||||
data-testid="search-box"
|
||||
|
||||
@@ -58,6 +58,12 @@ const columns: ColumnsType[] = [
|
||||
headerAlign: 'left',
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
field: 'version',
|
||||
title: 'Version',
|
||||
headerAlign: 'left',
|
||||
flex: 1,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,7 @@ import { TableToolbar } from '../../components/TableToolbar';
|
||||
import { CustomColumnHeading } from '../../components/CustomColumnHeading';
|
||||
import { Title } from '../../components/Title';
|
||||
import { cellStyles, UniversalDataGrid } from '../../components/Universal-DataGrid';
|
||||
import { currencyToString } from '../../utils/currency';
|
||||
import { unymToNym } from '../../utils/currency';
|
||||
import { Tooltip } from '../../components/Tooltip';
|
||||
import { BIG_DIPPER } from '../../api/constants';
|
||||
import { splice } from '../../utils';
|
||||
@@ -77,7 +77,7 @@ export const PageGateways: React.FC = () => {
|
||||
to={`/network-components/gateway/${params.row.identityKey}`}
|
||||
data-testid="pledge-amount"
|
||||
>
|
||||
{currencyToString(params.value)}
|
||||
{unymToNym(params.value, 6)}
|
||||
</MuiLink>
|
||||
),
|
||||
},
|
||||
@@ -128,7 +128,7 @@ export const PageGateways: React.FC = () => {
|
||||
field: 'owner',
|
||||
headerName: 'Owner',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Owner" />,
|
||||
width: 380,
|
||||
width: 180,
|
||||
headerAlign: 'left',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
@@ -142,6 +142,24 @@ export const PageGateways: React.FC = () => {
|
||||
</MuiLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'version',
|
||||
headerName: 'Version',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Version" />,
|
||||
width: 150,
|
||||
headerAlign: 'left',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<MuiLink
|
||||
sx={{ ...cellStyles }}
|
||||
href={`${BIG_DIPPER}/account/${params.value}`}
|
||||
target="_blank"
|
||||
data-testid="owner"
|
||||
>
|
||||
{params.value}
|
||||
</MuiLink>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const handlePageSize = (event: SelectChangeEvent<string>) => {
|
||||
|
||||
@@ -164,10 +164,10 @@ const PageMixnodeDetailWithState: React.FC = () => {
|
||||
{mixNode && (
|
||||
<ContentCard title="Location">
|
||||
{mixNode?.error && <ComponentError text="There was a problem retrieving this mixnode location" />}
|
||||
{mixNode.data && mixNode?.data?.location && (
|
||||
{mixNode?.data?.location?.latitude && mixNode?.data?.location?.longitude && (
|
||||
<WorldMap
|
||||
loading={mixNode.isLoading}
|
||||
userLocation={[mixNode?.data?.location?.lng, mixNode?.data?.location?.lat]}
|
||||
userLocation={[mixNode.data.location.longitude, mixNode.data.location.latitude]}
|
||||
/>
|
||||
)}
|
||||
</ContentCard>
|
||||
|
||||
@@ -77,8 +77,8 @@ export interface MixNodeResponseItem {
|
||||
status: MixnodeStatus;
|
||||
location: {
|
||||
country_name: string;
|
||||
lat: number;
|
||||
lng: number;
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
three_letter_iso_country_code: string;
|
||||
two_letter_iso_country_code: string;
|
||||
};
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-gateway"
|
||||
version = "1.1.4"
|
||||
version = "1.1.5"
|
||||
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.4"
|
||||
version = "1.1.5"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-validator-api"
|
||||
version = "1.1.4"
|
||||
version = "1.1.5"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
## UNRELEASED
|
||||
|
||||
## [nym-connect-v1.1.5](https://github.com/nymtech/nym/tree/nym-connect-v1.1.5) (2023-01-10)
|
||||
|
||||
- get version number from tauri and display by @fmtabbara in https://github.com/nymtech/nym/pull/2684
|
||||
- Feature/nym connect experimental software text by @fmtabbara in https://github.com/nymtech/nym/pull/2692
|
||||
- NymConnect - Display service info in tooltip **1.1.5 Release** by @fmtabbara in https://github.com/nymtech/nym/pull/2704
|
||||
- Feat/2130 tables update rebase by @gala1234 in https://github.com/nymtech/nym/pull/2742
|
||||
|
||||
## [nym-connect-v1.1.4](https://github.com/nymtech/nym/tree/nym-connect-v1.1.4) (2022-12-20)
|
||||
|
||||
This release contains the new opt-in Test & Earn program, and it uses a stress-tested directory of network requesters to improve reliability. It also has some bugfixes, performance improvements, and better error handling.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nym/nym-connect",
|
||||
"version": "1.1.4",
|
||||
"version": "1.1.5",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-connect"
|
||||
version = "1.1.4"
|
||||
version = "1.1.5"
|
||||
description = "nym-connect"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-connect",
|
||||
"version": "1.1.4"
|
||||
"version": "1.1.5"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import { Box, CircularProgress, Typography } from '@mui/material';
|
||||
import { Box, CircularProgress, Divider, Stack, Tooltip, Typography } from '@mui/material';
|
||||
import { DateTime } from 'luxon';
|
||||
import { ConnectionStatusKind } from '../types';
|
||||
import { ServiceProvider } from '../types/directory';
|
||||
import { ServiceProviderInfo } from './ServiceProviderInfo';
|
||||
|
||||
const FONT_SIZE = '10px';
|
||||
const FONT_WEIGHT = '600';
|
||||
@@ -14,8 +15,8 @@ const ConnectionStatusContent: React.FC<{
|
||||
switch (status) {
|
||||
case ConnectionStatusKind.connected:
|
||||
return (
|
||||
<Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} textAlign="center">
|
||||
Connected
|
||||
<Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} fontSize="14px">
|
||||
Connected to
|
||||
</Typography>
|
||||
);
|
||||
case ConnectionStatusKind.disconnecting:
|
||||
@@ -44,7 +45,7 @@ const ConnectionStatusContent: React.FC<{
|
||||
ml={1}
|
||||
textTransform="uppercase"
|
||||
textAlign="center"
|
||||
fontSize="10px"
|
||||
fontSize={FONT_SIZE}
|
||||
sx={{ wordSpacing: 3, letterSpacing: 2 }}
|
||||
>
|
||||
You are not protected
|
||||
@@ -59,7 +60,7 @@ export const ConnectionStatus: React.FC<{
|
||||
status: ConnectionStatusKind;
|
||||
connectedSince?: DateTime;
|
||||
serviceProvider?: ServiceProvider;
|
||||
}> = ({ status, connectedSince, serviceProvider }) => {
|
||||
}> = ({ status, serviceProvider }) => {
|
||||
const color =
|
||||
status === ConnectionStatusKind.connected || status === ConnectionStatusKind.disconnecting
|
||||
? '#21D072'
|
||||
@@ -70,13 +71,13 @@ export const ConnectionStatus: React.FC<{
|
||||
<Box color={color} fontSize={FONT_SIZE} sx={{ mb: 1 }}>
|
||||
<ConnectionStatusContent status={status} />
|
||||
</Box>
|
||||
<Box>
|
||||
{serviceProvider && (
|
||||
<Typography fontSize={12} textAlign="center">
|
||||
To {serviceProvider.description}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
{serviceProvider ? (
|
||||
<Tooltip title={<ServiceProviderInfo serviceProvider={serviceProvider} />}>
|
||||
<Box sx={{ cursor: 'pointer' }}>
|
||||
{serviceProvider && <Typography>{serviceProvider.description}</Typography>}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { Divider, Stack, Typography } from '@mui/material';
|
||||
import { ServiceProvider } from 'src/types/directory';
|
||||
|
||||
export const ServiceProviderInfo = ({ serviceProvider }: { serviceProvider: ServiceProvider }) => (
|
||||
<Stack gap={1} sx={{ wordWrap: 'break-word', maxWidth: 150, p: 1 }}>
|
||||
<Typography variant="body2" fontWeight="bold">
|
||||
Connection info
|
||||
</Typography>
|
||||
<Typography variant="caption">{serviceProvider.description}</Typography>
|
||||
<Divider />
|
||||
<Typography variant="caption" fontWeight="bold">
|
||||
Gateway <Typography variant="caption">{serviceProvider.gateway}</Typography>
|
||||
</Typography>
|
||||
<Divider />
|
||||
<Typography variant="caption" fontWeight="bold">
|
||||
Provider <Typography variant="caption">{serviceProvider.address.slice(0, 35)}...</Typography>
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
@@ -86,6 +86,8 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
.catch((e) => console.log(e));
|
||||
|
||||
listen('socks5-event', (e: TauriEvent) => {
|
||||
console.log(e);
|
||||
|
||||
setError(e.payload);
|
||||
}).then((result) => {
|
||||
unlisten.push(result);
|
||||
|
||||
@@ -37,11 +37,13 @@ export const ConnectedLayout: React.FC<{
|
||||
}) => (
|
||||
<>
|
||||
<IpAddressAndPortModal show={showInfoModal} onClose={handleCloseInfoModal} ipAddress={ipAddress} port={port} />
|
||||
<Box pb={4}>
|
||||
<Box>
|
||||
<ConnectionStatus status={ConnectionStatusKind.connected} serviceProvider={serviceProvider} />
|
||||
</Box>
|
||||
<IpAddressAndPort label="Socks5 address" ipAddress={ipAddress} port={port} />
|
||||
<Divider sx={{ my: 3 }} />
|
||||
<Divider sx={{ my: 2 }} />
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<IpAddressAndPort label="Socks5 address" ipAddress={ipAddress} port={port} />
|
||||
</Box>
|
||||
{/* <ConnectionStats stats={stats} /> */}
|
||||
<ConnectionTimer connectedSince={connectedSince} />
|
||||
<ConnectionButton status={status} busy={busy} onClick={onConnectClick} isError={isError} />
|
||||
|
||||
+11
-1
@@ -1,6 +1,16 @@
|
||||
# Changelog
|
||||
|
||||
## UNRELEASED
|
||||
## [nym-wallet-v1.1.6](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.6) (2023-01-10)
|
||||
|
||||
- wallet: rewrite some abci errors on the fly by @octol in https://github.com/nymtech/nym/pull/2716
|
||||
- Feature/node settings apy playground by @fmtabbara in https://github.com/nymtech/nym/pull/1677
|
||||
- Fix param input layout **1.1.5 Release** by @fmtabbara in https://github.com/nymtech/nym/pull/2720
|
||||
- Add epoch info to unbond modal **1.1.5 Release** by @fmtabbara in https://github.com/nymtech/nym/pull/2718
|
||||
- Feat/2130 tables update rebase by @gala1234ss as url param by @doums in https://github.com/nymtech/nym/pull/2780
|
||||
|
||||
## [nym-wallet-v1.1.5](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.5) (2022-12-22)
|
||||
|
||||
This release contains the APY calculator feature.
|
||||
|
||||
## [nym-wallet-v1.1.4](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.4) (2022-12-20)
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -2876,7 +2876,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym_wallet"
|
||||
version = "1.1.3"
|
||||
version = "1.1.5"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"argon2 0.3.4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nymproject/nym-wallet-app",
|
||||
"version": "1.1.4",
|
||||
"version": "1.1.6",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym_wallet"
|
||||
version = "1.1.4"
|
||||
version = "1.1.6"
|
||||
description = "Nym Native Wallet"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-wallet",
|
||||
"version": "1.1.4"
|
||||
"version": "1.1.6"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Box, Button, Chip, Stack, Tooltip, Typography } from '@mui/material';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
@@ -11,6 +11,9 @@ import { Node as NodeIcon } from '../../svg-icons/node';
|
||||
import { Cell, Header, NodeTable } from './NodeTable';
|
||||
import { BondedMixnodeActions, TBondedMixnodeActions } from './BondedMixnodeActions';
|
||||
import { NodeStats } from './NodeStats';
|
||||
import { getIntervalAsDate } from 'src/utils';
|
||||
|
||||
const textWhenNotName = 'This node has not yet set a name';
|
||||
|
||||
const headers: Header[] = [
|
||||
{
|
||||
@@ -63,6 +66,7 @@ export const BondedMixnode = ({
|
||||
network?: Network;
|
||||
onActionSelect: (action: TBondedMixnodeActions) => void;
|
||||
}) => {
|
||||
const [nextEpoch, setNextEpoch] = useState<string | Error>();
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
name,
|
||||
@@ -78,6 +82,15 @@ export const BondedMixnode = ({
|
||||
identityKey,
|
||||
host,
|
||||
} = mixnode;
|
||||
|
||||
const getNextInterval = async () => {
|
||||
try {
|
||||
const { nextEpoch } = await getIntervalAsDate();
|
||||
setNextEpoch(nextEpoch);
|
||||
} catch {
|
||||
setNextEpoch(Error());
|
||||
}
|
||||
};
|
||||
const cells: Cell[] = [
|
||||
{
|
||||
cell: `${stake.amount} ${stake.denom}`,
|
||||
@@ -121,6 +134,10 @@ export const BondedMixnode = ({
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
getNextInterval();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack gap={2}>
|
||||
<NymCard
|
||||
@@ -133,32 +150,43 @@ export const BondedMixnode = ({
|
||||
</Typography>
|
||||
<NodeStatus status={status} />
|
||||
</Box>
|
||||
{name && (
|
||||
<Tooltip title={host} arrow>
|
||||
<Typography fontWeight="regular" variant="h6" width="fit-content">
|
||||
{name}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
{name?.includes(textWhenNotName) ? null : (
|
||||
<Typography fontWeight="regular" variant="h6" width="fit-content">
|
||||
{name}
|
||||
</Typography>
|
||||
)}
|
||||
<IdentityKey identityKey={identityKey} />
|
||||
<Tooltip title={host} placement="top" arrow>
|
||||
<Box width="fit-content">
|
||||
<IdentityKey identityKey={identityKey} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
}
|
||||
Action={
|
||||
isMixnode(mixnode) && (
|
||||
<Tooltip title={mixnode.isUnbonding ? 'You have a pending unbond event. Node settings are disabled.' : ''}>
|
||||
<Box>
|
||||
<Button
|
||||
variant="text"
|
||||
color="secondary"
|
||||
onClick={() => navigate('/bonding/node-settings')}
|
||||
startIcon={<NodeIcon />}
|
||||
disabled={mixnode.isUnbonding}
|
||||
>
|
||||
Node Settings
|
||||
</Button>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)
|
||||
<Box display="flex" flexDirection="column" alignItems="flex-end" justifyContent="space-between" height={70}>
|
||||
{isMixnode(mixnode) && (
|
||||
<Tooltip
|
||||
title={mixnode.isUnbonding ? 'You have a pending unbond event. Node settings are disabled.' : ''}
|
||||
>
|
||||
<Box>
|
||||
<Button
|
||||
variant="text"
|
||||
color="secondary"
|
||||
onClick={() => navigate('/bonding/node-settings')}
|
||||
startIcon={<NodeIcon />}
|
||||
disabled={mixnode.isUnbonding}
|
||||
>
|
||||
Node Settings
|
||||
</Button>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
{nextEpoch instanceof Error ? null : (
|
||||
<Typography fontSize={14} marginRight={1}>
|
||||
Next epoch starts at <b>{nextEpoch}</b>
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<NodeTable headers={headers} cells={cells} />
|
||||
|
||||
@@ -14,6 +14,7 @@ export type ConfirmationDetailProps = {
|
||||
export const ConfirmationDetailsModal = ({
|
||||
title,
|
||||
subtitle,
|
||||
children,
|
||||
txUrl,
|
||||
status,
|
||||
onClose,
|
||||
@@ -23,6 +24,7 @@ export const ConfirmationDetailsModal = ({
|
||||
onClose: () => void;
|
||||
sx?: SxProps;
|
||||
backdropProps?: object;
|
||||
children?: React.ReactNode;
|
||||
}) => {
|
||||
if (status === 'error') {
|
||||
<ErrorModal open message={subtitle} onClose={onClose} />;
|
||||
@@ -45,6 +47,7 @@ export const ConfirmationDetailsModal = ({
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography>{subtitle}</Typography>
|
||||
{children}
|
||||
{txUrl && <Link href={txUrl} target="_blank" sx={{ ml: 1 }} text="View on blockchain" />}
|
||||
</Stack>
|
||||
</ConfirmationModal>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { Button, Stack, Typography, Grid, useMediaQuery, useTheme } from '@mui/material';
|
||||
import { Tune as TuneIcon, BorderColor as BorderColorIcon, Paid as PaidIcon } from '@mui/icons-material';
|
||||
import { Tune as TuneIcon, BorderColor as BorderColorIcon } from '@mui/icons-material';
|
||||
import { CoinMark } from '@nymproject/react/coins/CoinMark';
|
||||
import { NymCard, ClientAddress } from '../../components';
|
||||
import { SignMessageModal } from './SignMessageModal';
|
||||
import { PoweredByBity } from 'src/svg-icons';
|
||||
import { AppContext } from 'src/context';
|
||||
|
||||
// TODO retrieve this value from env
|
||||
const EXCHANGE_URL = 'https://buy.nymtech.net';
|
||||
@@ -51,6 +52,7 @@ const TutorialStep = ({
|
||||
);
|
||||
|
||||
export const Tutorial = () => {
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
const [showSignModal, setShowSignModal] = useState(false);
|
||||
const theme = useTheme();
|
||||
const showBorder = useMediaQuery(theme.breakpoints.up('md'));
|
||||
@@ -123,7 +125,12 @@ export const Tutorial = () => {
|
||||
<Button variant="outlined" size="large" onClick={() => setShowSignModal(true)}>
|
||||
Sign message
|
||||
</Button>
|
||||
<Button variant="contained" size="large" href={EXCHANGE_URL} target="_blank">
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large"
|
||||
href={`${EXCHANGE_URL}/?wallet_address=${clientDetails!.client_address}`}
|
||||
target="_blank"
|
||||
>
|
||||
Buy NYM
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
@@ -29,15 +29,18 @@ export const DelegationItem = ({
|
||||
}) => {
|
||||
const operatingCost = isDelegation(item) && item.cost_params?.interval_operating_cost;
|
||||
|
||||
const tooltipText = () => {
|
||||
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.';
|
||||
} else if (item.uses_vesting_contract_tokens) {
|
||||
return 'Delegation made with locked tockens';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
arrow
|
||||
title={
|
||||
nodeIsUnbonded
|
||||
? '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.'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<Tooltip arrow title={tooltipText()}>
|
||||
<TableRow key={item.node_identity} sx={{ color: !item.node_identity ? 'error.main' : 'inherit' }}>
|
||||
<TableCell sx={{ color: 'inherit', pr: 1 }} padding="normal">
|
||||
{nodeIsUnbonded ? (
|
||||
|
||||
@@ -18,6 +18,7 @@ import { NodeUnbondPage } from './settings-pages/NodeUnbondPage';
|
||||
import { createNavItems } from './node-settings.constant';
|
||||
import { isMixnode } from 'src/types';
|
||||
import { ApyPlayground } from './apy-playground';
|
||||
import { getIntervalAsDate } from 'src/utils';
|
||||
|
||||
export const NodeSettings = () => {
|
||||
const theme = useTheme();
|
||||
@@ -26,7 +27,7 @@ export const NodeSettings = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const [confirmationDetails, setConfirmationDetails] = useState<ConfirmationDetailProps>();
|
||||
const [confirmationDetails, setConfirmationDetails] = useState<ConfirmationDetailProps | undefined>();
|
||||
const [value, setValue] = React.useState('General');
|
||||
const handleChange = (event: React.SyntheticEvent, tab: string) => {
|
||||
setValue(tab);
|
||||
@@ -40,9 +41,11 @@ export const NodeSettings = () => {
|
||||
|
||||
const handleUnbond = async (fee?: FeeDetails) => {
|
||||
const tx = await unbond(fee);
|
||||
const { nextEpoch } = await getIntervalAsDate();
|
||||
setConfirmationDetails({
|
||||
status: 'success',
|
||||
title: 'Unbond successful',
|
||||
subtitle: `This operation will complete when the new epoch starts at: ${nextEpoch}`,
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
};
|
||||
@@ -135,7 +138,12 @@ export const NodeSettings = () => {
|
||||
setConfirmationDetails(undefined);
|
||||
navigate('/bonding');
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Typography fontWeight="bold">
|
||||
You should NOT shutdown your {isMixnode(bondedNode) ? 'mix node' : 'gateway'} until the unbond process is
|
||||
complete
|
||||
</Typography>
|
||||
</ConfirmationDetailsModal>
|
||||
)}
|
||||
</NymCard>
|
||||
</PageLayout>
|
||||
|
||||
+45
-67
@@ -1,24 +1,12 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Typography,
|
||||
TextField,
|
||||
InputAdornment,
|
||||
Grid,
|
||||
CircularProgress,
|
||||
Box,
|
||||
FormHelperText,
|
||||
} from '@mui/material';
|
||||
import { Button, Divider, Typography, TextField, InputAdornment, Grid, Box, FormHelperText } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { CurrencyDenom, MixNodeCostParams } from '@nymproject/types';
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||
import { add, format, fromUnixTime } from 'date-fns';
|
||||
import { isMixnode } from 'src/types';
|
||||
import {
|
||||
getCurrentInterval,
|
||||
getPendingIntervalEvents,
|
||||
simulateUpdateMixnodeCostParams,
|
||||
simulateVestingUpdateMixnodeCostParams,
|
||||
@@ -29,6 +17,7 @@ import { TBondedMixnode } from 'src/context/bonding';
|
||||
import { SimpleModal } from 'src/components/Modals/SimpleModal';
|
||||
import { bondedNodeParametersValidationSchema } from 'src/components/Bonding/forms/mixnodeValidationSchema';
|
||||
import { Console } from 'src/utils/console';
|
||||
import { getIntervalAsDate } from 'src/utils';
|
||||
import { Alert } from 'src/components/Alert';
|
||||
import { ChangeMixCostParams } from 'src/pages/bonding/types';
|
||||
import { AppContext } from 'src/context';
|
||||
@@ -39,7 +28,6 @@ import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
||||
export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }): JSX.Element => {
|
||||
const [openConfirmationModal, setOpenConfirmationModal] = useState<boolean>(false);
|
||||
const [intervalTime, setIntervalTime] = useState<string>();
|
||||
const [nextEpoch, setNextEpoch] = useState<string>();
|
||||
const [pendingUpdates, setPendingUpdates] = useState<MixNodeCostParams>();
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
const theme = useTheme();
|
||||
@@ -63,27 +51,13 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const getIntervalAsDate = async () => {
|
||||
const interval = await getCurrentInterval();
|
||||
const secondsToNextInterval =
|
||||
Number(interval.epochs_in_interval - interval.current_epoch_id) * Number(interval.epoch_length_seconds);
|
||||
|
||||
setIntervalTime(
|
||||
format(
|
||||
add(new Date(), {
|
||||
seconds: secondsToNextInterval,
|
||||
}),
|
||||
'MM/dd/yyyy HH:mm',
|
||||
),
|
||||
);
|
||||
setNextEpoch(
|
||||
format(
|
||||
add(fromUnixTime(Number(interval.current_epoch_start_unix)), {
|
||||
seconds: Number(interval.epoch_length_seconds),
|
||||
}),
|
||||
'HH:mm',
|
||||
),
|
||||
);
|
||||
const getCurrentInterval = async () => {
|
||||
try {
|
||||
const { nextInterval } = await getIntervalAsDate();
|
||||
setIntervalTime(nextInterval);
|
||||
} catch {
|
||||
console.log('cant retrieve next interval');
|
||||
}
|
||||
};
|
||||
|
||||
const getPendingEvents = async () => {
|
||||
@@ -107,7 +81,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getIntervalAsDate();
|
||||
getCurrentInterval();
|
||||
getPendingEvents();
|
||||
}, []);
|
||||
|
||||
@@ -137,7 +111,16 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container xs item>
|
||||
<Grid
|
||||
container
|
||||
xs
|
||||
item
|
||||
sx={{
|
||||
'& .MuiGrid-item': {
|
||||
pl: 0,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{fee && (
|
||||
<ConfirmTx
|
||||
open
|
||||
@@ -152,9 +135,6 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
||||
<Alert
|
||||
title={
|
||||
<>
|
||||
<Box component="span" sx={{ fontWeight: 600, mr: 2 }}>
|
||||
{`Next epoch ${nextEpoch}`}
|
||||
</Box>
|
||||
<Box component="span" sx={{ fontWeight: 600 }}>{`Next interval: ${intervalTime}`}</Box>
|
||||
</>
|
||||
}
|
||||
@@ -177,7 +157,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
||||
</Typography>
|
||||
</Grid>
|
||||
{isMixnode(bondedNode) && (
|
||||
<Grid item xs={12} xl={6}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<TextField
|
||||
{...register('profitMargin')}
|
||||
name="profitMargin"
|
||||
@@ -206,7 +186,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
<Divider flexItem />
|
||||
<Divider flexItem sx={{ position: 'relative', left: '-24px', width: 'calc(100% + 24px)' }} />
|
||||
<Grid item container direction="row" alignItems="left" justifyContent="space-between" padding={3} spacing={1}>
|
||||
<Grid item>
|
||||
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
|
||||
@@ -223,33 +203,31 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
||||
Changes to cost will be applied in the next interval.
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid spacing={3} container item alignItems="center" xs={12} md={6}>
|
||||
<Grid item width={1}>
|
||||
<CurrencyFormField
|
||||
required
|
||||
fullWidth
|
||||
label="Operating cost"
|
||||
onChanged={(newValue) => {
|
||||
setValue('operatorCost', newValue, { shouldValidate: true, shouldDirty: true });
|
||||
}}
|
||||
validationError={errors.operatorCost?.amount?.message}
|
||||
denom={clientDetails?.display_mix_denom || 'nym'}
|
||||
initialValue={defaultValues.operatorCost.amount}
|
||||
/>
|
||||
{pendingUpdates && (
|
||||
<FormHelperText>
|
||||
Your last change to{' '}
|
||||
<Typography variant="caption" fontWeight="bold">
|
||||
{pendingUpdates.interval_operating_cost.amount}{' '}
|
||||
{pendingUpdates?.interval_operating_cost.denom.toUpperCase()}{' '}
|
||||
</Typography>
|
||||
will be applied in the next interval
|
||||
</FormHelperText>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<CurrencyFormField
|
||||
required
|
||||
fullWidth
|
||||
label="Operating cost"
|
||||
onChanged={(newValue) => {
|
||||
setValue('operatorCost', newValue, { shouldValidate: true, shouldDirty: true });
|
||||
}}
|
||||
validationError={errors.operatorCost?.amount?.message}
|
||||
denom={clientDetails?.display_mix_denom || 'nym'}
|
||||
initialValue={defaultValues.operatorCost.amount}
|
||||
/>
|
||||
{pendingUpdates && (
|
||||
<FormHelperText>
|
||||
Your last change to{' '}
|
||||
<Typography variant="caption" fontWeight="bold">
|
||||
{pendingUpdates.interval_operating_cost.amount}{' '}
|
||||
{pendingUpdates?.interval_operating_cost.denom.toUpperCase()}{' '}
|
||||
</Typography>
|
||||
will be applied in the next interval
|
||||
</FormHelperText>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Divider flexItem />
|
||||
<Divider flexItem sx={{ position: 'relative', left: '-24px', width: 'calc(100% + 24px)' }} />
|
||||
<Grid container justifyContent="end">
|
||||
<Button
|
||||
size="large"
|
||||
|
||||
@@ -18,7 +18,7 @@ import { DelegationListItemActions } from '../../components/Delegation/Delegatio
|
||||
import { RedeemModal } from '../../components/Rewards/RedeemModal';
|
||||
import { DelegationModal, DelegationModalProps } from '../../components/Delegation/DelegationModal';
|
||||
import { backDropStyles, modalStyles } from '../../../.storybook/storiesStyles';
|
||||
import { toPercentIntegerString } from '../../utils';
|
||||
import { toPercentIntegerString, getIntervalAsDate } from 'src/utils';
|
||||
|
||||
const storybookStyles = (theme: Theme, isStorybook?: boolean, backdropProps?: object) =>
|
||||
isStorybook
|
||||
@@ -36,9 +36,9 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
const [confirmationModalProps, setConfirmationModalProps] = useState<DelegationModalProps | undefined>();
|
||||
const [currentDelegationListActionItem, setCurrentDelegationListActionItem] = useState<DelegationWithEverything>();
|
||||
const [saturationError, setSaturationError] = useState<{ action: 'compound' | 'delegate'; saturation: string }>();
|
||||
const [nextEpoch, setNextEpoch] = useState<string | Error>();
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const {
|
||||
clientDetails,
|
||||
network,
|
||||
@@ -75,6 +75,15 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
};
|
||||
};
|
||||
|
||||
const getNextInterval = async () => {
|
||||
try {
|
||||
const { nextEpoch } = await getIntervalAsDate();
|
||||
setNextEpoch(nextEpoch);
|
||||
} catch {
|
||||
setNextEpoch(Error());
|
||||
}
|
||||
};
|
||||
|
||||
// Refresh the rewards and delegations periodically when page is mounted
|
||||
useEffect(() => {
|
||||
const timer = setInterval(refresh, 1 * 60 * 1000); // every 1 minute
|
||||
@@ -83,6 +92,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
getNextInterval();
|
||||
}, [clientDetails, confirmationModalProps]);
|
||||
|
||||
const handleDelegationItemActionClick = (item: DelegationWithEverything, action: DelegationListItemActions) => {
|
||||
@@ -332,35 +342,45 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
<>
|
||||
<Paper elevation={0} sx={{ p: 3, mt: 4 }}>
|
||||
<Stack spacing={3}>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h6" lineHeight={1.334} fontWeight={600}>
|
||||
Delegations
|
||||
</Typography>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
{' '}
|
||||
<Box display="flex" flexDirection="column">
|
||||
<Typography variant="h6" lineHeight={1.334} fontWeight={600}>
|
||||
Delegations
|
||||
</Typography>
|
||||
{!!delegations?.length && (
|
||||
<Link
|
||||
href={`${urls(network).networkExplorer}/network-components/mixnodes/`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
text="Network Explorer"
|
||||
fontSize={14}
|
||||
fontWeight={theme.palette.mode === 'light' ? 400 : 600}
|
||||
noIcon
|
||||
marginTop={1.5}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
{!!delegations?.length && (
|
||||
<Link
|
||||
href={`${urls(network).networkExplorer}/network-components/mixnodes/`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
text="Network Explorer"
|
||||
fontSize={14}
|
||||
fontWeight={theme.palette.mode === 'light' ? 400 : 600}
|
||||
noIcon
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
disableElevation
|
||||
onClick={() => setShowNewDelegationModal(true)}
|
||||
sx={{ py: 1.5, px: 5, color: 'primary.contrastText', height: 'fit-content' }}
|
||||
>
|
||||
Delegate
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{!!delegations?.length && (
|
||||
<Box display="flex" justifyContent="space-between" alignItems="end">
|
||||
<RewardsSummary isLoading={isLoading} totalDelegation={totalDelegations} totalRewards={totalRewards} />
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
disableElevation
|
||||
onClick={() => setShowNewDelegationModal(true)}
|
||||
sx={{ py: 1.5, px: 5, color: 'primary.contrastText' }}
|
||||
>
|
||||
Delegate
|
||||
</Button>
|
||||
{nextEpoch instanceof Error ? null : (
|
||||
<Typography fontSize={14}>
|
||||
Next epoch starts at <b>{nextEpoch}</b>
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
{delegationsComponent(delegations)}
|
||||
|
||||
@@ -2,11 +2,20 @@ import { appWindow } from '@tauri-apps/api/window';
|
||||
import bs58 from 'bs58';
|
||||
import Big from 'big.js';
|
||||
import { valid } from 'semver';
|
||||
import { add, format, fromUnixTime } from 'date-fns';
|
||||
import { isValidRawCoin, DecCoin, MixNodeCostParams } from '@nymproject/types';
|
||||
import { TPoolOption } from 'src/components';
|
||||
import { getDefaultMixnodeCostParams, getLockedCoins, getSpendableCoins, userBalance } from '../requests';
|
||||
import {
|
||||
getCurrentInterval,
|
||||
getDefaultMixnodeCostParams,
|
||||
getLockedCoins,
|
||||
getSpendableCoins,
|
||||
userBalance,
|
||||
} from '../requests';
|
||||
import { Console } from './console';
|
||||
|
||||
export * from './nextEpoch';
|
||||
|
||||
export const validateKey = (key: string, bytesLength: number): boolean => {
|
||||
// it must be a valid base58 key
|
||||
try {
|
||||
@@ -198,3 +207,25 @@ export const unymToNym = (unym: string | Big, dp = 4) => {
|
||||
}
|
||||
return nym;
|
||||
};
|
||||
|
||||
export const getIntervalAsDate = async () => {
|
||||
const interval = await getCurrentInterval();
|
||||
const secondsToNextInterval =
|
||||
Number(interval.epochs_in_interval - interval.current_epoch_id) * Number(interval.epoch_length_seconds);
|
||||
|
||||
const nextInterval = format(
|
||||
add(new Date(), {
|
||||
seconds: secondsToNextInterval,
|
||||
}),
|
||||
'dd/MM/yyyy, HH:mm',
|
||||
);
|
||||
|
||||
const nextEpoch = format(
|
||||
add(fromUnixTime(Number(interval.current_epoch_start_unix)), {
|
||||
seconds: Number(interval.epoch_length_seconds),
|
||||
}),
|
||||
'HH:mm',
|
||||
);
|
||||
|
||||
return { nextEpoch, nextInterval };
|
||||
};
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { getCurrentInterval } from 'src/requests';
|
||||
import { add, format, fromUnixTime } from 'date-fns';
|
||||
|
||||
export const getIntervalAsDate = async () => {
|
||||
const interval = await getCurrentInterval();
|
||||
const secondsToNextInterval =
|
||||
Number(interval.epochs_in_interval - interval.current_epoch_id) * Number(interval.epoch_length_seconds);
|
||||
|
||||
const intervalTime = format(
|
||||
add(new Date(), {
|
||||
seconds: secondsToNextInterval,
|
||||
}),
|
||||
'dd/MM/yyyy, HH:mm',
|
||||
);
|
||||
const nextEpoch = format(
|
||||
add(fromUnixTime(Number(interval.current_epoch_start_unix)), {
|
||||
seconds: Number(interval.epoch_length_seconds),
|
||||
}),
|
||||
'HH:mm',
|
||||
);
|
||||
|
||||
return { intervalTime, nextEpoch };
|
||||
};
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.4"
|
||||
version = "1.1.5"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-network-statistics"
|
||||
version = "1.1.4"
|
||||
version = "1.1.5"
|
||||
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.4"
|
||||
version = "1.1.5"
|
||||
authors = ["Nym Technologies SA"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user