Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 165e7d8b27 | |||
| 3c97d0d16b | |||
| bc55c10e19 | |||
| a925c39642 | |||
| d23fb366e4 | |||
| 3d500c25c5 | |||
| 9348722b84 | |||
| 726a406797 | |||
| 4652d65874 | |||
| a4ca94ccef | |||
| 24839770ff | |||
| 7ac3ec3598 | |||
| 77ae71eba4 | |||
| d4b836277e | |||
| b92ee84874 | |||
| 2eb0ce381a | |||
| 9f42f0152b | |||
| 5217edcca3 | |||
| e306effdac | |||
| dc2b1c6d2a | |||
| 4232801e80 | |||
| 96df3ad4ce | |||
| d614a2b81b | |||
| d27245e184 | |||
| 5dbfcadfdb | |||
| 035dada0e0 | |||
| 1d867156e3 | |||
| ed9be47ec4 | |||
| 3aa2e6c54d | |||
| eb96fc72b9 | |||
| 59cec6f03c | |||
| c0a0d89a90 | |||
| 9881a94757 | |||
| 76b07d487b |
@@ -0,0 +1,112 @@
|
||||
name: Build and upload binaries to CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'clients/**'
|
||||
- 'common/**'
|
||||
- 'contracts/**'
|
||||
- 'explorer-api/**'
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'mixnode/**'
|
||||
- 'sdk/rust/nym-sdk/**'
|
||||
- 'service-providers/**'
|
||||
- 'nym-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'tools/nym-cli/**'
|
||||
- 'tools/ts-rs-cli/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'clients/**'
|
||||
- 'common/**'
|
||||
- 'contracts/**'
|
||||
- 'explorer-api/**'
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'mixnode/**'
|
||||
- 'sdk/rust/nym-sdk/**'
|
||||
- 'service-providers/**'
|
||||
- 'nym-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'tools/nym-cli/**'
|
||||
- 'tools/ts-rs-cli/**'
|
||||
|
||||
env:
|
||||
NETWORK: mainnet
|
||||
|
||||
jobs:
|
||||
publish-nym:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-20.04]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare build output directory
|
||||
shell: bash
|
||||
env:
|
||||
OUTPUT_DIR: ci-builds/${{ github.ref_name }}
|
||||
run: |
|
||||
rm -rf ci-builds || true
|
||||
mkdir -p $OUTPUT_DIR
|
||||
echo $OUTPUT_DIR
|
||||
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
|
||||
continue-on-error: true
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --release
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Build release contracts
|
||||
run: make wasm
|
||||
|
||||
- name: Prepare build output
|
||||
shell: bash
|
||||
env:
|
||||
OUTPUT_DIR: ci-builds/${{ github.ref_name }}
|
||||
run: |
|
||||
cp target/release/nym-client $OUTPUT_DIR
|
||||
cp target/release/nym-gateway $OUTPUT_DIR
|
||||
cp target/release/nym-mixnode $OUTPUT_DIR
|
||||
cp target/release/nym-socks5-client $OUTPUT_DIR
|
||||
cp target/release/nym-api $OUTPUT_DIR
|
||||
cp target/release/nym-network-requester $OUTPUT_DIR
|
||||
cp target/release/nym-network-statistics $OUTPUT_DIR
|
||||
cp target/release/nym-cli $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
|
||||
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-avzr"
|
||||
SOURCE: "ci-builds/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
+11
-4
@@ -4,13 +4,20 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
# [Unreleased]
|
||||
|
||||
# [v1.1.9] (2023-02-07)
|
||||
|
||||
### Added
|
||||
- Remove Coconut feature flag ([#2793])
|
||||
- Separate `nym-api` endpoints with values of "total-supply" and "circulating-supply" in `nym` ([#2964])
|
||||
|
||||
- remove coconut feature and unify builds ([#2890])
|
||||
- native-client: is now capable of listening for requests on sockets different than `127.0.0.1` ([#2939]). This can be specified via `--host` flag during `init` or `run`. Alternatively a custom `host` can be set in `config.toml` file under `socket` section.
|
||||
### Changed
|
||||
- native-client: is now capable of listening for requests on sockets different than `127.0.0.1` ([#2912]). This can be specified via `--host` flag during `init` or `run`. Alternatively a custom `host` can be set in `config.toml` file under `socket` section.
|
||||
- mixnode, gateway: fix unexpected shutdown on corrupted connection ([#2963])
|
||||
|
||||
[#2890]: https://github.com/nymtech/nym/pull/2890
|
||||
[#2939]: https://github.com/nymtech/nym/pull/2939
|
||||
[#2793]: https://github.com/nymtech/nym/issues/2793
|
||||
[#2912]: https://github.com/nymtech/nym/issues/2912
|
||||
[#2964]: https://github.com/nymtech/nym/issues/2964
|
||||
[#2963]: https://github.com/nymtech/nym/issues/3017
|
||||
|
||||
# [v1.1.8] (2023-01-31)
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -3438,7 +3438,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.8"
|
||||
version = "1.1.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "client-core"
|
||||
version = "1.1.8"
|
||||
version = "1.1.9"
|
||||
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.8"
|
||||
version = "1.1.9"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -31,7 +31,7 @@ pub(crate) struct Init {
|
||||
force_register_gateway: bool,
|
||||
|
||||
/// Comma separated list of rest endpoints of the nyxd validators
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',')]
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',', hide = true)]
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the API validators
|
||||
@@ -62,7 +62,7 @@ pub(crate) struct Init {
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[clap(long)]
|
||||
#[clap(long, hide = true)]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
|
||||
/// Save a summary of the initialization to a json file
|
||||
|
||||
@@ -23,7 +23,7 @@ pub(crate) struct Run {
|
||||
id: String,
|
||||
|
||||
/// Comma separated list of rest endpoints of the nyxd validators
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',')]
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',', hide = true)]
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the API validators
|
||||
@@ -59,7 +59,7 @@ pub(crate) struct Run {
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[clap(long)]
|
||||
#[clap(long, hide = true)]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.8"
|
||||
version = "1.1.9"
|
||||
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"
|
||||
|
||||
@@ -43,7 +43,7 @@ pub(crate) struct Init {
|
||||
force_register_gateway: bool,
|
||||
|
||||
/// Comma separated list of rest endpoints of the nyxd validators
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',')]
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',', hide = true)]
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the API validators
|
||||
@@ -66,7 +66,7 @@ pub(crate) struct Init {
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[clap(long)]
|
||||
#[clap(long, hide = true)]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
|
||||
/// Save a summary of the initialization to a json file
|
||||
|
||||
@@ -43,7 +43,7 @@ pub(crate) struct Run {
|
||||
gateway: Option<identity::PublicKey>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the nyxd validators
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',')]
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',', hide = true)]
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the Nym APIs
|
||||
@@ -65,7 +65,7 @@ pub(crate) struct Run {
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[clap(long)]
|
||||
#[clap(long, hide = true)]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.1.8"
|
||||
version = "1.1.9"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
## UNRELEASED
|
||||
|
||||
- nothing yet
|
||||
|
||||
## [nym-explorer-v1.0.5](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.5) (2023-02-14)
|
||||
|
||||
- NE - link `Owner` field on the node detail page to the account details on NG explorer ([#2923])
|
||||
- NE - Upgrade Sandbox and make below changes: ([#2332])
|
||||
|
||||
[#2923]: https://github.com/nymtech/nym/issues/2923
|
||||
[#2332]: https://github.com/nymtech/nym/issues/2332
|
||||
|
||||
## [nym-explorer-v1.0.4](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.4) (2023-01-31)
|
||||
|
||||
- Add routing score on gateway list ([#2913])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nym/network-explorer",
|
||||
"version": "1.0.4",
|
||||
"version": "1.0.5",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
|
||||
import { Link, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Tooltip } from '@nymproject/react/tooltip/Tooltip';
|
||||
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard';
|
||||
@@ -37,9 +37,19 @@ function formatCellValues(val: string | number, field: string) {
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (field === 'bond') {
|
||||
return unymToNym(val, 6);
|
||||
}
|
||||
|
||||
if (field === 'owner') {
|
||||
return (
|
||||
<Link underline="none" color="inherit" target="_blank" href={`https://mixnet.explorers.guru/account/${val}`}>
|
||||
{val}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-gateway"
|
||||
version = "1.1.8"
|
||||
version = "1.1.9"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
@@ -55,7 +55,8 @@ pub struct Init {
|
||||
long,
|
||||
alias = "validators",
|
||||
alias = "nymd_validators",
|
||||
value_delimiter = ','
|
||||
value_delimiter = ',',
|
||||
hide = true
|
||||
)]
|
||||
// the alias here is included for backwards compatibility (1.1.4 and before)
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
@@ -66,7 +67,7 @@ pub struct Init {
|
||||
|
||||
/// Set this gateway to work only with coconut credentials; that would disallow clients to
|
||||
/// bypass bandwidth credential requirement
|
||||
#[clap(long)]
|
||||
#[clap(long, hide = true)]
|
||||
only_coconut_credentials: Option<bool>,
|
||||
|
||||
/// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server
|
||||
|
||||
@@ -53,7 +53,8 @@ pub struct Run {
|
||||
long,
|
||||
alias = "validators",
|
||||
alias = "nymd_validators",
|
||||
value_delimiter = ','
|
||||
value_delimiter = ',',
|
||||
hide = true
|
||||
)]
|
||||
// the alias here is included for backwards compatibility (1.1.4 and before)
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
@@ -64,7 +65,7 @@ pub struct Run {
|
||||
|
||||
/// Set this gateway to work only with coconut credentials; that would disallow clients to
|
||||
/// bypass bandwidth credential requirement
|
||||
#[clap(long)]
|
||||
#[clap(long, hide = true)]
|
||||
only_coconut_credentials: Option<bool>,
|
||||
|
||||
/// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server
|
||||
|
||||
@@ -181,6 +181,7 @@ impl<St: Storage> ConnectionHandler<St> {
|
||||
mut shutdown: TaskClient,
|
||||
) {
|
||||
debug!("Starting connection handler for {:?}", remote);
|
||||
shutdown.mark_as_success();
|
||||
let mut framed_conn = Framed::new(conn, SphinxCodec);
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
|
||||
+3
-2
@@ -4,7 +4,8 @@
|
||||
"nym-wallet",
|
||||
"nym-connect",
|
||||
"nym-connect-android",
|
||||
"sdk/typescript/**"
|
||||
"sdk/typescript/examples/docs",
|
||||
"sdk/typescript/packages/**"
|
||||
],
|
||||
"version": "0.0.0"
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-mixnode"
|
||||
version = "1.1.9"
|
||||
version = "1.1.10"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
@@ -77,6 +77,7 @@ impl ConnectionHandler {
|
||||
mut shutdown: TaskClient,
|
||||
) {
|
||||
debug!("Starting connection handler for {:?}", remote);
|
||||
shutdown.mark_as_success();
|
||||
let mut framed_conn = Framed::new(conn, SphinxCodec);
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-api"
|
||||
version = "1.1.8"
|
||||
version = "1.1.10"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
@@ -15,7 +15,11 @@ pub(crate) mod routes;
|
||||
|
||||
/// Merges the routes with http information and returns it to Rocket for serving
|
||||
pub(crate) fn circulating_supply_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![settings: routes::get_circulating_supply]
|
||||
openapi_get_routes_spec![
|
||||
settings: routes::get_full_circulating_supply,
|
||||
routes::get_total_supply,
|
||||
routes::get_circulating_supply
|
||||
]
|
||||
}
|
||||
|
||||
/// Spawn the circulating supply cache refresher.
|
||||
|
||||
@@ -1,15 +1,30 @@
|
||||
use rocket::http::Status;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::circulating_supply_api::cache::CirculatingSupplyCache;
|
||||
use crate::node_status_api::models::ErrorResponse;
|
||||
use nym_api_requests::models::CirculatingSupplyResponse;
|
||||
use rocket::http::Status;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
use rocket_okapi::openapi;
|
||||
use validator_client::nyxd::Coin;
|
||||
|
||||
// TODO: this is not the best place to put it, it should be more centralised,
|
||||
// but for a quick fix, that's good enough for now...
|
||||
// (for proper solution we should be managing `NymNetworkDetails` via rocket and grabbing display exponent
|
||||
// value from the mix denom here.
|
||||
const UNYM_RATIO: f64 = 1000000.;
|
||||
|
||||
fn unym_coin_to_float_unym(coin: Coin) -> f64 {
|
||||
// our total supply can't exceed 1B so an overflow here is impossible
|
||||
// (if it happened, then we SHOULD crash)
|
||||
coin.amount as f64 / UNYM_RATIO
|
||||
}
|
||||
|
||||
#[openapi(tag = "circulating-supply")]
|
||||
#[get("/circulating-supply")]
|
||||
pub(crate) async fn get_circulating_supply(
|
||||
pub(crate) async fn get_full_circulating_supply(
|
||||
cache: &State<CirculatingSupplyCache>,
|
||||
) -> Result<Json<CirculatingSupplyResponse>, ErrorResponse> {
|
||||
match cache.get_circulating_supply().await {
|
||||
@@ -20,3 +35,43 @@ pub(crate) async fn get_circulating_supply(
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[openapi(tag = "circulating-supply")]
|
||||
#[get("/circulating-supply/total-supply-value")]
|
||||
pub(crate) async fn get_total_supply(
|
||||
cache: &State<CirculatingSupplyCache>,
|
||||
) -> Result<Json<f64>, ErrorResponse> {
|
||||
let full_circulating_supply = match cache.get_circulating_supply().await {
|
||||
Some(res) => res,
|
||||
None => {
|
||||
return Err(ErrorResponse::new(
|
||||
"unavailable",
|
||||
Status::InternalServerError,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Json(unym_coin_to_float_unym(
|
||||
full_circulating_supply.total_supply.into(),
|
||||
)))
|
||||
}
|
||||
|
||||
#[openapi(tag = "circulating-supply")]
|
||||
#[get("/circulating-supply/circulating-supply-value")]
|
||||
pub(crate) async fn get_circulating_supply(
|
||||
cache: &State<CirculatingSupplyCache>,
|
||||
) -> Result<Json<f64>, ErrorResponse> {
|
||||
let full_circulating_supply = match cache.get_circulating_supply().await {
|
||||
Some(res) => res,
|
||||
None => {
|
||||
return Err(ErrorResponse::new(
|
||||
"unavailable",
|
||||
Status::InternalServerError,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Json(unym_coin_to_float_unym(
|
||||
full_circulating_supply.circulating_supply.into(),
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -93,11 +93,16 @@ pub(crate) struct CliArgs {
|
||||
pub(crate) enabled_credentials_mode: Option<bool>,
|
||||
|
||||
/// Announced address where coconut clients will connect.
|
||||
#[clap(long)]
|
||||
#[clap(long, hide = true)]
|
||||
pub(crate) announce_address: Option<url::Url>,
|
||||
|
||||
/// Flag to indicate whether coconut signer authority is enabled on this API
|
||||
#[clap(long, requires = "mnemonic", requires = "announce_address")]
|
||||
#[clap(
|
||||
long,
|
||||
requires = "mnemonic",
|
||||
requires = "announce_address",
|
||||
hide = true
|
||||
)]
|
||||
pub(crate) enable_coconut: Option<bool>,
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,20 @@
|
||||
|
||||
## UNRELEASED
|
||||
|
||||
## [nym-connect-v1.1.9](https://github.com/nymtech/nym/tree/nym-connect-v1.1.9) (2023-02-14)
|
||||
|
||||
- Button animations ([#2949])
|
||||
- add effect when the button is clicked ([#2947])
|
||||
- UI to select gateways based on some performance criteria by checking gateways' routing score from nym-api ([#2942])
|
||||
- client health check when connecting ([#2859])
|
||||
- allow user to select own gateway ([#2952])
|
||||
|
||||
[#2952]: https://github.com/nymtech/nym/issues/2952
|
||||
[#2949]: https://github.com/nymtech/nym/issues/2949
|
||||
[#2947]: https://github.com/nymtech/nym/issues/2947
|
||||
[#2942]: https://github.com/nymtech/nym/issues/2942
|
||||
[#2859]: https://github.com/nymtech/nym/issues/2859
|
||||
|
||||
## [nym-connect-v1.1.8](https://github.com/nymtech/nym/tree/nym-connect-v1.1.8) (2023-01-31)
|
||||
|
||||
- Add supported apps in the menu + update guide ([#2868])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nym/nym-connect",
|
||||
"version": "1.1.8",
|
||||
"version": "1.1.9",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-connect"
|
||||
version = "1.1.8"
|
||||
version = "1.1.9"
|
||||
description = "nym-connect"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
@@ -45,6 +45,8 @@ url = "2.2"
|
||||
yaml-rust = "0.4"
|
||||
|
||||
client-core = { path = "../../clients/client-core" }
|
||||
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
|
||||
contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common"}
|
||||
config-common = { path = "../../common/config", package = "config" }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
logging = { path = "../../common/logging"}
|
||||
|
||||
@@ -52,6 +52,7 @@ fn main() {
|
||||
crate::operations::connection::status::get_gateway_connection_status,
|
||||
crate::operations::connection::status::start_connection_health_check_task,
|
||||
crate::operations::directory::get_services,
|
||||
crate::operations::directory::get_gateways_detailed,
|
||||
crate::operations::export::export_keys,
|
||||
crate::operations::window::hide_window,
|
||||
crate::operations::growth::test_and_earn::growth_tne_get_client_id,
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::models::{DirectoryService, HarbourMasterService, PagedResult};
|
||||
use crate::models::{
|
||||
DirectoryService, DirectoryServiceProvider, HarbourMasterService, PagedResult,
|
||||
};
|
||||
use contracts_common::types::Percent;
|
||||
use nym_api_requests::models::GatewayBondAnnotated;
|
||||
|
||||
static SERVICE_PROVIDER_WELLKNOWN_URL: &str =
|
||||
"https://nymtech.net/.wellknown/connect/service-providers.json";
|
||||
|
||||
static HARBOUR_MASTER_URL: &str = "https://harbourmaster.nymtech.net/v1/services/?size=100";
|
||||
|
||||
static GATEWAYS_DETAILED_URL: &str =
|
||||
"https://validator.nymtech.net/api/v1/status/gateways/detailed";
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_services() -> Result<Vec<DirectoryService>> {
|
||||
pub async fn get_services() -> Result<Vec<DirectoryServiceProvider>> {
|
||||
log::trace!("Fetching services");
|
||||
let res = reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL)
|
||||
let services_res = reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL)
|
||||
.await?
|
||||
.json::<Vec<DirectoryService>>()
|
||||
.await?;
|
||||
log::trace!("Received: {:#?}", res);
|
||||
log::trace!("Received: {:#?}", services_res);
|
||||
|
||||
log::trace!("Fetching gateways");
|
||||
let gateway_res = reqwest::get(GATEWAYS_DETAILED_URL)
|
||||
.await?
|
||||
.json::<Vec<GatewayBondAnnotated>>()
|
||||
.await?;
|
||||
log::trace!("Received: {:#?}", gateway_res);
|
||||
|
||||
// TODO: get paged
|
||||
log::trace!("Fetching active services");
|
||||
@@ -27,7 +41,7 @@ pub async fn get_services() -> Result<Vec<DirectoryService>> {
|
||||
|
||||
let mut filtered: Vec<DirectoryService> = vec![];
|
||||
|
||||
for service in &res {
|
||||
for service in &services_res {
|
||||
let items: _ = service
|
||||
.items
|
||||
.clone()
|
||||
@@ -47,5 +61,32 @@ pub async fn get_services() -> Result<Vec<DirectoryService>> {
|
||||
})
|
||||
}
|
||||
|
||||
Ok(filtered)
|
||||
let perf_threshold = Percent::from_percentage_value(90).unwrap();
|
||||
|
||||
// Use only services that are active AND have a performance of >= 90%
|
||||
let services_with_good_performance: Vec<DirectoryServiceProvider> = filtered
|
||||
.iter_mut()
|
||||
.fold(vec![], |mut acc, sp| {
|
||||
acc.append(&mut sp.items);
|
||||
acc
|
||||
})
|
||||
.into_iter()
|
||||
.filter(|sp| {
|
||||
gateway_res.iter().any(|gateway| {
|
||||
gateway.gateway_bond.gateway.identity_key == sp.gateway
|
||||
&& gateway.performance >= perf_threshold
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(services_with_good_performance)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_gateways_detailed() -> Result<Vec<GatewayBondAnnotated>> {
|
||||
let res = reqwest::get(GATEWAYS_DETAILED_URL)
|
||||
.await?
|
||||
.json::<Vec<GatewayBondAnnotated>>()
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-connect",
|
||||
"version": "1.1.8"
|
||||
"version": "1.1.9"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { forage } from '@tauri-apps/tauri-forage';
|
||||
import { useClientContext } from './context/main';
|
||||
import { useTauriEvents } from './utils';
|
||||
import { AppRoutes } from './routes';
|
||||
import { Connected } from './pages/connection/Connected';
|
||||
|
||||
export const App: FCWithChildren = () => {
|
||||
const context = useClientContext();
|
||||
const [busy, setBusy] = React.useState<boolean>();
|
||||
|
||||
useTauriEvents('help://clear-storage', (_event) => {
|
||||
console.log('About to clear local storage...');
|
||||
// clear local storage
|
||||
try {
|
||||
forage.clear()();
|
||||
console.log('Local storage cleared');
|
||||
} catch (e) {
|
||||
console.error('Failed to clear local storage', e);
|
||||
}
|
||||
});
|
||||
|
||||
const handleConnectClick = React.useCallback(async () => {
|
||||
const currentStatus = context.connectionStatus;
|
||||
if (currentStatus === 'connected' || currentStatus === 'disconnected') {
|
||||
setBusy(true);
|
||||
|
||||
// eslint-disable-next-line default-case
|
||||
switch (currentStatus) {
|
||||
case 'disconnected':
|
||||
await context.startConnecting();
|
||||
context.setConnectedSince(DateTime.now());
|
||||
break;
|
||||
case 'connected':
|
||||
await context.startDisconnecting();
|
||||
context.setConnectedSince(undefined);
|
||||
break;
|
||||
}
|
||||
setBusy(false);
|
||||
}
|
||||
}, [context.connectionStatus]);
|
||||
|
||||
if (context.connectionStatus === 'disconnected' || context.connectionStatus === 'connecting') {
|
||||
return <AppRoutes />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Connected
|
||||
status={context.connectionStatus}
|
||||
showInfoModal={context.showInfoModal}
|
||||
closeInfoModal={() => context.setShowInfoModal(false)}
|
||||
busy={busy}
|
||||
onConnectClick={handleConnectClick}
|
||||
ipAddress="127.0.0.1"
|
||||
port={1080}
|
||||
gatewayPerformance={context.gatewayPerformance}
|
||||
connectedSince={context.connectedSince}
|
||||
serviceProvider={context.selectedProvider}
|
||||
stats={[
|
||||
{
|
||||
label: 'in:',
|
||||
totalBytes: 1024,
|
||||
rateBytesPerSecond: 1024 * 1024 * 1024 + 10,
|
||||
},
|
||||
{
|
||||
label: 'out:',
|
||||
totalBytes: 1024 * 1024 * 1024 * 1024 * 20,
|
||||
rateBytesPerSecond: 1024 * 1024 + 10,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,10 +1,28 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useClientContext } from 'src/context/main';
|
||||
import { CustomTitleBar } from './CustomTitleBar';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
|
||||
export const AppWindowFrame: FCWithChildren = ({ children }) => {
|
||||
const location = useLocation();
|
||||
const { userDefinedGateway, setUserDefinedGateway } = useClientContext();
|
||||
|
||||
// defined functions to be used when moving away from pages
|
||||
const onBack = () => {
|
||||
switch (location.pathname) {
|
||||
case '/menu/settings':
|
||||
return () => {
|
||||
// when the user moves away from the settings page and the gateway is not valid
|
||||
// set isActive to false
|
||||
if (!userDefinedGateway?.gateway) {
|
||||
setUserDefinedGateway((current) => ({ ...current, isActive: false }));
|
||||
}
|
||||
};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -14,7 +32,7 @@ export const AppWindowFrame: FCWithChildren = ({ children }) => {
|
||||
height: '100vh',
|
||||
}}
|
||||
>
|
||||
<CustomTitleBar path={location.pathname} />
|
||||
<CustomTitleBar path={location.pathname} onBack={onBack()} />
|
||||
<Box style={{ padding: '16px' }}>{children}</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Box, CircularProgress, Tooltip, Typography } from '@mui/material';
|
||||
import { DateTime } from 'luxon';
|
||||
import { ErrorOutline, InfoOutlined } from '@mui/icons-material';
|
||||
import { ConnectionStatusKind, GatewayPerformance } from '../types';
|
||||
import { ServiceProvider } from '../types/directory';
|
||||
import { GatwayWarningInfo, ServiceProviderInfo } from './TooltipInfo';
|
||||
import { ErrorOutline, InfoOutlined } from '@mui/icons-material';
|
||||
|
||||
const FONT_SIZE = '14px';
|
||||
const FONT_WEIGHT = '600';
|
||||
@@ -83,17 +83,14 @@ export const ConnectionStatus: FCWithChildren<{
|
||||
serviceProvider?: ServiceProvider;
|
||||
}> = ({ status, serviceProvider, gatewayPerformance }) => {
|
||||
const color = status === 'connected' || status === 'disconnecting' ? '#21D072' : 'white';
|
||||
console.log(gatewayPerformance);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box color={color} sx={{ mb: 2 }}>
|
||||
<ConnectionStatusContent
|
||||
status={status}
|
||||
serviceProvider={serviceProvider}
|
||||
gatewayError={gatewayPerformance !== 'Good'}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
<Box color={color} sx={{ mb: 2 }}>
|
||||
<ConnectionStatusContent
|
||||
status={status}
|
||||
serviceProvider={serviceProvider}
|
||||
gatewayError={gatewayPerformance !== 'Good'}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -29,9 +29,13 @@ const MenuIcon = () => {
|
||||
return <CustomButton Icon={Menu} onClick={() => navigate('/menu')} />;
|
||||
};
|
||||
|
||||
const ArrowBackIcon = () => {
|
||||
const ArrowBackIcon = ({ onBack }: { onBack?: () => void }) => {
|
||||
const navigate = useNavigate();
|
||||
return <CustomButton Icon={ArrowBack} onClick={() => navigate(-1)} />;
|
||||
const handleBack = () => {
|
||||
onBack?.();
|
||||
navigate(-1);
|
||||
};
|
||||
return <CustomButton Icon={ArrowBack} onClick={handleBack} />;
|
||||
};
|
||||
|
||||
const getTitleIcon = (path: string) => {
|
||||
@@ -46,16 +50,14 @@ const getTitleIcon = (path: string) => {
|
||||
return <NymWordmark width={36} />;
|
||||
};
|
||||
|
||||
export const CustomTitleBar = ({ path = '/' }: { path?: string }) => {
|
||||
return (
|
||||
<Box data-tauri-drag-region style={customTitleBarStyles.titlebar}>
|
||||
{/* set width to keep logo centered */}
|
||||
<Box sx={{ width: '40px' }}>{path === '/' ? <MenuIcon /> : <ArrowBackIcon />}</Box>
|
||||
{getTitleIcon(path)}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<CustomButton Icon={Minimize} onClick={() => appWindow.minimize()} />
|
||||
<CustomButton Icon={Close} onClick={() => appWindow.close()} />
|
||||
</Box>
|
||||
export const CustomTitleBar = ({ path = '/', onBack }: { path?: string; onBack?: () => void }) => (
|
||||
<Box data-tauri-drag-region style={customTitleBarStyles.titlebar}>
|
||||
{/* set width to keep logo centered */}
|
||||
<Box sx={{ width: '40px' }}>{path === '/' ? <MenuIcon /> : <ArrowBackIcon onBack={onBack} />}</Box>
|
||||
{getTitleIcon(path)}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<CustomButton Icon={Minimize} onClick={() => appWindow.minimize()} />
|
||||
<CustomButton Icon={Close} onClick={() => appWindow.close()} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,6 @@ import Content from './content/en.yaml';
|
||||
import { useClientContext } from '../../context/main';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
import { NymShipyardTheme } from '../../theme';
|
||||
import { ConnectionStatusKind } from '../../types';
|
||||
|
||||
export const Wrapper: FCWithChildren<{ disabled: boolean }> = ({ disabled, children }) => {
|
||||
if (disabled) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import { forage } from '@tauri-apps/tauri-forage';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { ClientId, DrawEntry, Draws, Registration } from './types';
|
||||
import { useClientContext } from '../../../context/main';
|
||||
import { ConnectionStatusKind } from '../../../types';
|
||||
|
||||
export type TTestAndEarnContext = {
|
||||
loadedOnce: boolean;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
|
||||
export const HelpImage = ({ img, imageDescription }: { img: string; imageDescription: string }) => (
|
||||
<img src={img} alt={imageDescription} width="100%" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Box, Button, Typography } from '@mui/material';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { InfoModal } from './InfoModal';
|
||||
import { CopyToClipboard } from './CopyToClipboard';
|
||||
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ConnectionStatusKind } from 'src/types';
|
||||
|
||||
const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => {
|
||||
if (isError && hover) {
|
||||
return '#21D072';
|
||||
}
|
||||
if (isError) {
|
||||
return '#40475C';
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case 'disconnected':
|
||||
if (hover) {
|
||||
return '#FFF';
|
||||
}
|
||||
return '#BBB';
|
||||
case 'connecting':
|
||||
return '#FFF';
|
||||
case 'disconnecting':
|
||||
return '#FFF';
|
||||
default:
|
||||
// connected
|
||||
if (hover) {
|
||||
return '#E43E3E';
|
||||
}
|
||||
return '#21D072';
|
||||
}
|
||||
};
|
||||
|
||||
export const PowerButton: FCWithChildren<{
|
||||
onClick?: (status: ConnectionStatusKind) => void;
|
||||
isError?: boolean;
|
||||
disabled?: boolean;
|
||||
status: ConnectionStatusKind;
|
||||
busy?: boolean;
|
||||
}> = ({ onClick, disabled, status, isError }) => {
|
||||
const [hover, setHover] = React.useState<boolean>(false);
|
||||
|
||||
const handleClick = () => {
|
||||
if (disabled === true) {
|
||||
return;
|
||||
}
|
||||
if (onClick) {
|
||||
onClick(status);
|
||||
}
|
||||
};
|
||||
|
||||
const statusFillColor = getStatusFillColor(status, hover, Boolean(isError));
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="190"
|
||||
height="190"
|
||||
viewBox="0 0 200 200"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
onClick={handleClick}
|
||||
style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}
|
||||
onMouseEnter={() => !disabled && setHover(true)}
|
||||
onMouseLeave={() => !disabled && setHover(false)}
|
||||
>
|
||||
<g transform="translate(-30, -25) ">
|
||||
<circle cx={131} cy={131} r={70} strokeWidth={2} stroke={statusFillColor} filter="url(#blur)" opacity="0.6" />
|
||||
<circle cx={131} cy={131} r={22} strokeWidth={1} stroke={statusFillColor} filter="url(#blur)" opacity="0.3" />
|
||||
<circle opacity={0.6} cx={131} cy={131} r={68.5} stroke={statusFillColor} />
|
||||
<g filter="url(#filter1_d_944_9033)">
|
||||
<circle cx={131} cy={131} r={64} fill="url(#paint1_radial_944_9033)" />
|
||||
<circle cx={131} cy={131} r={63} stroke={statusFillColor} strokeWidth={2} />
|
||||
</g>
|
||||
<g opacity={0.5} filter="url(#filter2_f_944_9033)">
|
||||
<g clipPath="url(#clip0_944_9033)">
|
||||
<path
|
||||
d="M131 113C129.9 113 129 113.9 129 115V131C129 132.1 129.9 133 131 133C132.1 133 133 132.1 133 131V115C133 113.9 132.1 113 131 113ZM141.28 118.72C140.5 119.5 140.52 120.72 141.26 121.5C143.52 123.9 144.92 127.1 145 130.64C145.18 138.3 138.84 144.9 131.18 144.98C123.36 145.1 117 138.8 117 131C117 127.32 118.42 123.98 120.74 121.48C121.48 120.7 121.48 119.48 120.72 118.72C119.92 117.92 118.62 117.94 117.86 118.76C114.96 121.84 113.14 125.94 113 130.48C112.72 140.24 120.66 148.68 130.42 148.98C140.62 149.3 149 141.12 149 130.98C149 126.24 147.16 121.96 144.16 118.76C143.4 117.94 142.08 117.92 141.28 118.72Z"
|
||||
stroke={statusFillColor}
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g clipPath="url(#clip1_944_9033)">
|
||||
<path
|
||||
d="M131 113C129.9 113 129 113.9 129 115V131C129 132.1 129.9 133 131 133C132.1 133 133 132.1 133 131V115C133 113.9 132.1 113 131 113ZM141.28 118.72C140.5 119.5 140.52 120.72 141.26 121.5C143.52 123.9 144.92 127.1 145 130.64C145.18 138.3 138.84 144.9 131.18 144.98C123.36 145.1 117 138.8 117 131C117 127.32 118.42 123.98 120.74 121.48C121.48 120.7 121.48 119.48 120.72 118.72C119.92 117.92 118.62 117.94 117.86 118.76C114.96 121.84 113.14 125.94 113 130.48C112.72 140.24 120.66 148.68 130.42 148.98C140.62 149.3 149 141.12 149 130.98C149 126.24 147.16 121.96 144.16 118.76C143.4 117.94 142.08 117.92 141.28 118.72Z"
|
||||
fill={statusFillColor}
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_f_944_9033"
|
||||
x={0}
|
||||
y={0}
|
||||
width={240}
|
||||
height={240}
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity={0} result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation={40} result="effect1_foregroundBlur_944_9033" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_d_944_9033"
|
||||
x={52}
|
||||
y={58}
|
||||
width={158}
|
||||
height={158}
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity={0} result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_944_9033" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_944_9033" result="shape" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter2_f_944_9033"
|
||||
x={97}
|
||||
y={97}
|
||||
width={68}
|
||||
height={68}
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity={0} result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation={5} result="effect1_foregroundBlur_944_9033" />
|
||||
</filter>
|
||||
<filter id="blur">
|
||||
<feGaussianBlur stdDeviation="5" />
|
||||
</filter>
|
||||
</defs>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { ConnectionStatusKind } from 'src/types';
|
||||
import './power-button.css';
|
||||
|
||||
const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => {
|
||||
if (isError && hover) {
|
||||
return '#21D072';
|
||||
}
|
||||
if (isError) {
|
||||
return '#40475C';
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case 'disconnected':
|
||||
if (hover) {
|
||||
return '#FFF';
|
||||
}
|
||||
return '#BBB';
|
||||
case 'connecting':
|
||||
return '#FFF';
|
||||
case 'disconnecting':
|
||||
return '#FFF';
|
||||
default:
|
||||
// connected
|
||||
if (hover) {
|
||||
return '#E43E3E';
|
||||
}
|
||||
return '#21D072';
|
||||
}
|
||||
};
|
||||
|
||||
export const PowerButton: FCWithChildren<{
|
||||
onClick?: (status: ConnectionStatusKind) => void;
|
||||
isError?: boolean;
|
||||
disabled?: boolean;
|
||||
status: ConnectionStatusKind;
|
||||
busy?: boolean;
|
||||
}> = ({ onClick, disabled, status, isError }) => {
|
||||
const [hover, setHover] = React.useState<boolean>(false);
|
||||
|
||||
const handleClick = () => {
|
||||
if (disabled === true) {
|
||||
return;
|
||||
}
|
||||
if (onClick) {
|
||||
onClick(status);
|
||||
}
|
||||
};
|
||||
|
||||
const statusFillColor = getStatusFillColor(status, hover, Boolean(isError));
|
||||
|
||||
const getClassName = useCallback(() => {
|
||||
if (hover) {
|
||||
switch (status) {
|
||||
case 'disconnected':
|
||||
return 'expand';
|
||||
default:
|
||||
return 'contract';
|
||||
}
|
||||
}
|
||||
if (!hover) {
|
||||
switch (status) {
|
||||
case 'connected':
|
||||
return 'expand';
|
||||
default:
|
||||
return 'contract';
|
||||
}
|
||||
}
|
||||
return 'contract';
|
||||
}, [status, hover]);
|
||||
|
||||
const buttonPulse = () => {
|
||||
if (status === 'connecting' || status === 'disconnecting') return 'pulse';
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="190"
|
||||
height="190"
|
||||
viewBox="0 0 200 200"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
onClick={handleClick}
|
||||
style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}
|
||||
onMouseEnter={() => !disabled && setHover(true)}
|
||||
onMouseLeave={() => !disabled && setHover(false)}
|
||||
>
|
||||
<g transform="translate(-30, -25) ">
|
||||
<circle cx={131} cy={131} r={75} strokeWidth={4} stroke={statusFillColor} filter="url(#blur)" opacity="0.6" />
|
||||
<circle cx={131} cy={131} r={25} strokeWidth={2} stroke={statusFillColor} filter="url(#blur)" opacity="0.5" />
|
||||
<g id="Button power">
|
||||
<circle cx="131" cy="131" r="68.5" stroke={statusFillColor} strokeWidth="0.5" />
|
||||
<circle id="ring-one" className={getClassName()} cx="131" cy="131" r="73" stroke={statusFillColor} />
|
||||
<circle id="ring-two" className={getClassName()} cx="131" cy="131" r="77" stroke={statusFillColor} />
|
||||
<circle id="ring-three" className={getClassName()} cx="131" cy="131" r="81" stroke={statusFillColor} />
|
||||
<circle id="ring-four" className={getClassName()} cx="131" cy="131" r="85" stroke={statusFillColor} />
|
||||
<g id="button bg">
|
||||
<circle cx="131" cy="131" r="63" stroke={statusFillColor} strokeWidth="3" className={buttonPulse()} />
|
||||
</g>
|
||||
<g id="Power icon">
|
||||
<g id="Icon">
|
||||
<g id="Group 672_2">
|
||||
<g id="power_settings_new_black_24dp (1) 1_2" clipPath="url(#clip1_944_8739)">
|
||||
<path
|
||||
id="Vector_2"
|
||||
d="M131 113C129.9 113 129 113.9 129 115V131C129 132.1 129.9 133 131 133C132.1 133 133 132.1 133 131V115C133 113.9 132.1 113 131 113ZM141.28 118.72C140.5 119.5 140.52 120.72 141.26 121.5C143.52 123.9 144.92 127.1 145 130.64C145.18 138.3 138.84 144.9 131.18 144.98C123.36 145.1 117 138.8 117 131C117 127.32 118.42 123.98 120.74 121.48C121.48 120.7 121.48 119.48 120.72 118.72C119.92 117.92 118.62 117.94 117.86 118.76C114.96 121.84 113.14 125.94 113 130.48C112.72 140.24 120.66 148.68 130.42 148.98C140.62 149.3 149 141.12 149 130.98C149 126.24 147.16 121.96 144.16 118.76C143.4 117.94 142.08 117.92 141.28 118.72Z"
|
||||
fill={statusFillColor}
|
||||
className={buttonPulse()}
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="blur" width="200%" height="200%" x="-50%" y="-50%">
|
||||
<feGaussianBlur stdDeviation="12.5" />
|
||||
</filter>
|
||||
</defs>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
#ring-expand {
|
||||
animation-name: rings-expand;
|
||||
animation-duration: 0.4s;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
#ring-one.expand {
|
||||
opacity: 0.3;
|
||||
transition: opacity 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
#ring-two.expand {
|
||||
opacity: 0.2;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
#ring-three.expand {
|
||||
opacity: 0.1;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
#ring-four.expand {
|
||||
opacity: 0.05;
|
||||
transition: opacity 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
#ring-one.contract {
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
#ring-two.contract {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
#ring-three.contract {
|
||||
opacity: 0;
|
||||
transition: opacity 0.1s;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
#ring-four.contract {
|
||||
opacity: 0;
|
||||
transition: opacity 0.1s;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
circle,
|
||||
path {
|
||||
transition: stroke 0.5s, fill 0.5s;
|
||||
}
|
||||
|
||||
.pulse {
|
||||
animation-name: pulse;
|
||||
animation-duration: 0.5s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState, useRef } from 'react';
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import type { UnlistenFn } from '@tauri-apps/api/event';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { forage } from '@tauri-apps/tauri-forage';
|
||||
import { Error } from 'src/types/error';
|
||||
import { TauriEvent } from 'src/types/event';
|
||||
import { getVersion } from '@tauri-apps/api/app';
|
||||
import { useEvents } from 'src/hooks/events';
|
||||
import { UserDefinedGateway } from 'src/types/gateway';
|
||||
import { forage } from '@tauri-apps/tauri-forage';
|
||||
import { ConnectionStatusKind, GatewayPerformance } from '../types';
|
||||
import { ConnectionStatsItem } from '../components/ConnectionStats';
|
||||
import { ServiceProvider, Services } from '../types/directory';
|
||||
import { ServiceProvider } from '../types/directory';
|
||||
|
||||
const TAURI_EVENT_STATUS_CHANGED = 'app:connection-status-changed';
|
||||
const FORAGE_KEY = 'nym-connect-user-gateway';
|
||||
|
||||
type ModeType = 'light' | 'dark';
|
||||
|
||||
@@ -25,6 +24,7 @@ export type TClientContext = {
|
||||
gatewayPerformance: GatewayPerformance;
|
||||
selectedProvider?: ServiceProvider;
|
||||
showInfoModal: boolean;
|
||||
userDefinedGateway?: UserDefinedGateway;
|
||||
setMode: (mode: ModeType) => void;
|
||||
clearError: () => void;
|
||||
setConnectionStatus: (connectionStatus: ConnectionStatusKind) => void;
|
||||
@@ -34,6 +34,7 @@ export type TClientContext = {
|
||||
setRandomSerivceProvider: () => void;
|
||||
startConnecting: () => Promise<void>;
|
||||
startDisconnecting: () => Promise<void>;
|
||||
setUserDefinedGateway: React.Dispatch<React.SetStateAction<UserDefinedGateway>>;
|
||||
};
|
||||
|
||||
export const ClientContext = createContext({} as TClientContext);
|
||||
@@ -49,29 +50,51 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
const [appVersion, setAppVersion] = useState<string>();
|
||||
const [gatewayPerformance, setGatewayPerformance] = useState<GatewayPerformance>('Good');
|
||||
const [showInfoModal, setShowInfoModal] = useState(false);
|
||||
const [userDefinedGateway, setUserDefinedGateway] = useState<UserDefinedGateway>({ isActive: false, gateway: '' });
|
||||
|
||||
const getAppVersion = async () => {
|
||||
const version = await getVersion();
|
||||
return version;
|
||||
};
|
||||
|
||||
const timerId = useRef<NodeJS.Timeout>();
|
||||
const setUserGatewayInStorage = async (gateway: UserDefinedGateway) => {
|
||||
try {
|
||||
await forage.setItem({
|
||||
key: FORAGE_KEY,
|
||||
value: gateway,
|
||||
} as any)();
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const flattenProviders = (services: Services) => {
|
||||
return services.reduce((a: ServiceProvider[], b) => {
|
||||
return [...a, ...b.items];
|
||||
}, []);
|
||||
const getUserGatewayFromStorage = async (): Promise<UserDefinedGateway | undefined> => {
|
||||
try {
|
||||
const gatewayFromStorage = await forage.getItem({ key: FORAGE_KEY })();
|
||||
return gatewayFromStorage;
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const initialiseApp = async () => {
|
||||
const services = await invoke('get_services');
|
||||
const allServiceProviders = flattenProviders(services as Services);
|
||||
const AppVersion = await getAppVersion();
|
||||
const storedUserDefinedGateway = await getUserGatewayFromStorage();
|
||||
|
||||
setAppVersion(AppVersion);
|
||||
setServiceProviders(allServiceProviders);
|
||||
setServiceProviders(services as ServiceProvider[]);
|
||||
if (storedUserDefinedGateway) setUserDefinedGateway(storedUserDefinedGateway);
|
||||
};
|
||||
|
||||
useEvents({
|
||||
onError: (e) => setError(e),
|
||||
onGatewayPerformanceChange: (performance) => setGatewayPerformance(performance),
|
||||
onStatusChange: (status) => setConnectionStatus(status),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
initialiseApp();
|
||||
}, []);
|
||||
@@ -84,49 +107,6 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
})();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten: UnlistenFn[] = [];
|
||||
|
||||
// TODO: fix typings
|
||||
listen(TAURI_EVENT_STATUS_CHANGED, (event) => {
|
||||
const { status } = event.payload as any;
|
||||
console.log(TAURI_EVENT_STATUS_CHANGED, { status, event });
|
||||
setConnectionStatus(status);
|
||||
})
|
||||
.then((result) => {
|
||||
unlisten.push(result);
|
||||
})
|
||||
.catch((e) => console.log(e));
|
||||
|
||||
listen('socks5-event', (e: TauriEvent) => {
|
||||
console.log(e);
|
||||
|
||||
setError(e.payload);
|
||||
}).then((result) => {
|
||||
unlisten.push(result);
|
||||
});
|
||||
|
||||
listen('socks5-status-event', (e: TauriEvent) => {
|
||||
if (e.payload.message.includes('slow')) {
|
||||
setGatewayPerformance('Poor');
|
||||
|
||||
if (timerId.current) {
|
||||
clearTimeout(timerId.current);
|
||||
}
|
||||
|
||||
timerId.current = setTimeout(() => {
|
||||
setGatewayPerformance('Good');
|
||||
}, 10000);
|
||||
}
|
||||
}).then((result) => {
|
||||
unlisten.push(result);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlisten.forEach((unsubscribe) => unsubscribe());
|
||||
};
|
||||
}, []);
|
||||
|
||||
const startConnecting = useCallback(async () => {
|
||||
try {
|
||||
await invoke('start_connecting');
|
||||
@@ -144,37 +124,19 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setServiceProvider = async (newServiceProvider?: ServiceProvider) => {
|
||||
if (newServiceProvider) {
|
||||
await invoke('set_gateway', { gateway: newServiceProvider.gateway });
|
||||
await invoke('set_service_provider', { serviceProvider: newServiceProvider.address });
|
||||
}
|
||||
const shouldUseUserGateway = !!userDefinedGateway.gateway && userDefinedGateway.isActive;
|
||||
|
||||
const setServiceProvider = async (newServiceProvider: ServiceProvider) => {
|
||||
await invoke('set_gateway', {
|
||||
gateway: newServiceProvider.gateway,
|
||||
});
|
||||
await invoke('set_service_provider', { serviceProvider: newServiceProvider.address });
|
||||
};
|
||||
|
||||
const setSpInStorage = async (sp: ServiceProvider) => {
|
||||
await forage.setItem({
|
||||
key: 'nym-connect-sp',
|
||||
value: sp,
|
||||
} as any)();
|
||||
};
|
||||
const getRandomSPFromList = (services: ServiceProvider[]) => {
|
||||
const randomSelection = services[Math.floor(Math.random() * services.length)];
|
||||
|
||||
const removeSpFromStorage = async () => {
|
||||
await forage.removeItem({
|
||||
key: 'nym-connect-sp',
|
||||
})();
|
||||
};
|
||||
|
||||
const getSpFromStorage = async (): Promise<ServiceProvider | undefined> => {
|
||||
try {
|
||||
const spFromStorage = await forage.getItem({ key: 'nym-connect-sp' })();
|
||||
return spFromStorage;
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
};
|
||||
|
||||
const getRandomSPFromList = (serviceProviders: ServiceProvider[]) => {
|
||||
const randomSelection = serviceProviders[Math.floor(Math.random() * serviceProviders.length)];
|
||||
if (shouldUseUserGateway) return { ...randomSelection, gateway: userDefinedGateway.gateway } as ServiceProvider;
|
||||
return randomSelection;
|
||||
};
|
||||
|
||||
@@ -182,8 +144,10 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
if (serviceProviders) {
|
||||
const randomServiceProvider = getRandomSPFromList(serviceProviders);
|
||||
await setServiceProvider(randomServiceProvider);
|
||||
await setUserGatewayInStorage(userDefinedGateway);
|
||||
setSelectedProvider(randomServiceProvider);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const clearError = () => setError(undefined);
|
||||
@@ -208,6 +172,8 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
startDisconnecting,
|
||||
gatewayPerformance,
|
||||
setShowInfoModal,
|
||||
userDefinedGateway,
|
||||
setUserDefinedGateway,
|
||||
}),
|
||||
[
|
||||
mode,
|
||||
@@ -220,6 +186,7 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
connectedSince,
|
||||
gatewayPerformance,
|
||||
selectedProvider,
|
||||
userDefinedGateway,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ const mockValues: TClientContext = {
|
||||
selectedProvider: { id: '1', description: 'Keybase service provider', gateway: 'abc123', address: '123abc' },
|
||||
gatewayPerformance: 'Good',
|
||||
showInfoModal: false,
|
||||
userDefinedGateway: { isActive: false, gateway: '' },
|
||||
setShowInfoModal: () => {},
|
||||
setMode: () => {},
|
||||
clearError: () => {},
|
||||
@@ -18,6 +19,7 @@ const mockValues: TClientContext = {
|
||||
startConnecting: async () => {},
|
||||
startDisconnecting: async () => {},
|
||||
setRandomSerivceProvider: () => {},
|
||||
setUserDefinedGateway: () => {},
|
||||
};
|
||||
|
||||
export const MockProvider: FCWithChildren<{
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { listen, UnlistenFn } from '@tauri-apps/api/event';
|
||||
import { ConnectionStatusKind, GatewayPerformance } from 'src/types';
|
||||
import { Error } from 'src/types/error';
|
||||
import { TauriEvent } from 'src/types/event';
|
||||
|
||||
const TAURI_EVENT_STATUS_CHANGED = 'app:connection-status-changed';
|
||||
|
||||
export const useEvents = ({
|
||||
onError,
|
||||
onStatusChange,
|
||||
onGatewayPerformanceChange,
|
||||
}: {
|
||||
onError: (error: Error) => void;
|
||||
onStatusChange: (status: ConnectionStatusKind) => void;
|
||||
onGatewayPerformanceChange: (status: GatewayPerformance) => void;
|
||||
}) => {
|
||||
const timerId = useRef<NodeJS.Timeout>();
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten: UnlistenFn[] = [];
|
||||
|
||||
// TODO: fix typings
|
||||
listen(TAURI_EVENT_STATUS_CHANGED, (event) => {
|
||||
const { status } = event.payload as any;
|
||||
console.log(TAURI_EVENT_STATUS_CHANGED, { status, event });
|
||||
onStatusChange(status);
|
||||
})
|
||||
.then((result) => {
|
||||
unlisten.push(result);
|
||||
})
|
||||
.catch((e) => console.log(e));
|
||||
|
||||
listen('socks5-event', (e: TauriEvent) => {
|
||||
console.log(e);
|
||||
onError(e.payload);
|
||||
}).then((result) => {
|
||||
unlisten.push(result);
|
||||
});
|
||||
|
||||
listen('socks5-status-event', (e: TauriEvent) => {
|
||||
if (e.payload.message.includes('slow')) {
|
||||
onGatewayPerformanceChange('Poor');
|
||||
|
||||
if (timerId?.current) {
|
||||
clearTimeout(timerId.current);
|
||||
}
|
||||
|
||||
timerId.current = setTimeout(() => {
|
||||
onGatewayPerformanceChange('Good');
|
||||
}, 10000);
|
||||
}
|
||||
}).then((result) => {
|
||||
unlisten.push(result);
|
||||
});
|
||||
|
||||
listen('socks5-connection-fail-event', (e: TauriEvent) => {
|
||||
onError({ title: 'Connection failed', message: `${e.payload.message} - Please disconnect and reconnect.` });
|
||||
onGatewayPerformanceChange('Poor');
|
||||
}).then((result) => {
|
||||
unlisten.push(result);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlisten.forEach((unsubscribe) => unsubscribe());
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
@@ -1,15 +1,14 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { GlobalStyles } from '@mui/material';
|
||||
import { ClientContextProvider } from './context/main';
|
||||
import { ErrorFallback } from './components/Error';
|
||||
import { NymMixnetTheme } from './theme';
|
||||
import { App } from './App';
|
||||
import { AppWindowFrame } from './components/AppWindowFrame';
|
||||
import { TestAndEarnContextProvider } from './components/Growth/context/TestAndEarnContext';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { AppRoutes } from './routes';
|
||||
import { GlobalStyles } from '@mui/material';
|
||||
|
||||
const elem = document.getElementById('root');
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { Box, Stack } from '@mui/material';
|
||||
import { DateTime } from 'luxon';
|
||||
import { IpAddressAndPortModal } from 'src/components/IpAddressAndPortModal';
|
||||
@@ -10,9 +10,12 @@ import { IpAddressAndPort } from 'src/components/IpAddressAndPort';
|
||||
import { ServiceProvider } from 'src/types/directory';
|
||||
import { ExperimentalWarning } from 'src/components/ExperimentalWarning';
|
||||
import { ConnectionLayout } from 'src/layouts/ConnectionLayout';
|
||||
import { PowerButton } from 'src/components/PowerButton';
|
||||
import { PowerButton } from 'src/components/PowerButton/PowerButton';
|
||||
import { Error } from 'src/types/error';
|
||||
import { InfoModal } from 'src/components/InfoModal';
|
||||
|
||||
export const Connected: FCWithChildren<{
|
||||
error?: Error;
|
||||
status: ConnectionStatusKind;
|
||||
showInfoModal: boolean;
|
||||
gatewayPerformance: GatewayPerformance;
|
||||
@@ -23,9 +26,11 @@ export const Connected: FCWithChildren<{
|
||||
busy?: boolean;
|
||||
isError?: boolean;
|
||||
serviceProvider?: ServiceProvider;
|
||||
clearError: () => void;
|
||||
onConnectClick: (status: ConnectionStatusKind) => void;
|
||||
closeInfoModal: () => void;
|
||||
}> = ({
|
||||
error,
|
||||
status,
|
||||
showInfoModal,
|
||||
gatewayPerformance,
|
||||
@@ -35,41 +40,41 @@ export const Connected: FCWithChildren<{
|
||||
busy,
|
||||
isError,
|
||||
serviceProvider,
|
||||
clearError,
|
||||
onConnectClick,
|
||||
closeInfoModal,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<IpAddressAndPortModal show={showInfoModal} onClose={closeInfoModal} ipAddress={ipAddress} port={port} />
|
||||
<ConnectionLayout
|
||||
TopContent={
|
||||
<Box>
|
||||
<ConnectionStatus
|
||||
status={ConnectionStatusKind.connected}
|
||||
gatewayPerformance={gatewayPerformance}
|
||||
serviceProvider={serviceProvider}
|
||||
/>
|
||||
<ConnectionTimer connectedSince={connectedSince} />
|
||||
</Box>
|
||||
}
|
||||
ConnectButton={
|
||||
<PowerButton
|
||||
status={status}
|
||||
busy={busy}
|
||||
onClick={onConnectClick}
|
||||
isError={isError}
|
||||
disabled={status === 'connecting' || status === 'disconnecting'}
|
||||
}) => (
|
||||
<>
|
||||
{error && <InfoModal show title={error.title} description={error.message} onClose={clearError} />}
|
||||
<IpAddressAndPortModal show={showInfoModal} onClose={closeInfoModal} ipAddress={ipAddress} port={port} />
|
||||
<ConnectionLayout
|
||||
TopContent={
|
||||
<Box>
|
||||
<ConnectionStatus
|
||||
status={ConnectionStatusKind.connected}
|
||||
gatewayPerformance={gatewayPerformance}
|
||||
serviceProvider={serviceProvider}
|
||||
/>
|
||||
}
|
||||
BottomContent={
|
||||
<Stack justifyContent="space-between">
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<IpAddressAndPort label="Socks5 address" ipAddress={ipAddress} port={port} />
|
||||
</Box>
|
||||
<ExperimentalWarning />
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
<ConnectionTimer connectedSince={connectedSince} />
|
||||
</Box>
|
||||
}
|
||||
ConnectButton={
|
||||
<PowerButton
|
||||
status={status}
|
||||
busy={busy}
|
||||
onClick={onConnectClick}
|
||||
isError={isError}
|
||||
disabled={status === 'disconnecting'}
|
||||
/>
|
||||
}
|
||||
BottomContent={
|
||||
<Stack justifyContent="space-between">
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<IpAddressAndPort label="Socks5 address" ipAddress={ipAddress} port={port} />
|
||||
</Box>
|
||||
<ExperimentalWarning />
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -2,14 +2,12 @@ import React from 'react';
|
||||
import { Stack, Typography } from '@mui/material';
|
||||
import { ConnectionStatus } from 'src/components/ConnectionStatus';
|
||||
import { ConnectionTimer } from 'src/components/ConntectionTimer';
|
||||
import { useClientContext } from 'src/context/main';
|
||||
import { InfoModal } from 'src/components/InfoModal';
|
||||
import { Error } from 'src/types/error';
|
||||
import { ExperimentalWarning } from 'src/components/ExperimentalWarning';
|
||||
import { ServiceProvider, Services } from 'src/types/directory';
|
||||
import { ConnectionStatusKind } from 'src/types';
|
||||
import { ConnectionButton } from 'src/components/ConnectionButton';
|
||||
import { PowerButton } from 'src/components/PowerButton';
|
||||
import { PowerButton } from 'src/components/PowerButton/PowerButton';
|
||||
import { Box } from '@mui/system';
|
||||
import { ConnectionLayout } from 'src/layouts/ConnectionLayout';
|
||||
|
||||
@@ -22,34 +20,32 @@ export const Disconnected: FCWithChildren<{
|
||||
serviceProvider?: ServiceProvider;
|
||||
clearError: () => void;
|
||||
onConnectClick: (status: ConnectionStatusKind) => void;
|
||||
}> = ({ status, error, onConnectClick, clearError, serviceProvider }) => {
|
||||
return (
|
||||
<>
|
||||
{error && <InfoModal show title={error.title} description={error.message} onClose={clearError} />}
|
||||
<ConnectionLayout
|
||||
TopContent={
|
||||
<Box>
|
||||
<ConnectionStatus status={ConnectionStatusKind.disconnected} gatewayPerformance="Good" />
|
||||
<ConnectionTimer />
|
||||
</Box>
|
||||
}
|
||||
ConnectButton={<PowerButton onClick={onConnectClick} status={status} disabled={false} />}
|
||||
BottomContent={
|
||||
<Stack justifyContent="space-between" pt={1}>
|
||||
<Typography
|
||||
fontWeight={600}
|
||||
textTransform="uppercase"
|
||||
textAlign="center"
|
||||
fontSize="12px"
|
||||
sx={{ wordSpacing: 1.5, letterSpacing: 1.5 }}
|
||||
color="warning.main"
|
||||
>
|
||||
You are not protected
|
||||
</Typography>
|
||||
<ExperimentalWarning />
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}> = ({ status, error, onConnectClick, clearError }) => (
|
||||
<>
|
||||
{error && <InfoModal show title={error.title} description={error.message} onClose={clearError} />}
|
||||
<ConnectionLayout
|
||||
TopContent={
|
||||
<Box>
|
||||
<ConnectionStatus status={ConnectionStatusKind.disconnected} gatewayPerformance="Good" />
|
||||
<ConnectionTimer />
|
||||
</Box>
|
||||
}
|
||||
ConnectButton={<PowerButton onClick={onConnectClick} status={status} disabled={status === 'connecting'} />}
|
||||
BottomContent={
|
||||
<Stack justifyContent="space-between" pt={1}>
|
||||
<Typography
|
||||
fontWeight={600}
|
||||
textTransform="uppercase"
|
||||
textAlign="center"
|
||||
fontSize="12px"
|
||||
sx={{ wordSpacing: 1.5, letterSpacing: 1.5 }}
|
||||
color="warning.main"
|
||||
>
|
||||
You are not protected
|
||||
</Typography>
|
||||
<ExperimentalWarning />
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -47,6 +47,8 @@ export const ConnectionPage = () => {
|
||||
if (context.connectionStatus === 'connected')
|
||||
return (
|
||||
<Connected
|
||||
error={context.error}
|
||||
clearError={context.clearError}
|
||||
status={context.connectionStatus}
|
||||
showInfoModal={context.showInfoModal}
|
||||
busy={busy}
|
||||
|
||||
@@ -19,8 +19,8 @@ export const CompatibleApps = () => (
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
|
||||
<Box sx={{ mb: 4 }}>
|
||||
{appsSchema.messagingApps.map((app, i) => (
|
||||
<Typography variant="body2" color="grey.400" sx={{ mb: 2 }} key={i}>
|
||||
{appsSchema.messagingApps.map((app) => (
|
||||
<Typography variant="body2" color="grey.400" sx={{ mb: 2 }} key={app}>
|
||||
{app}
|
||||
</Typography>
|
||||
))}
|
||||
@@ -32,8 +32,8 @@ export const CompatibleApps = () => (
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
|
||||
<Box sx={{ mb: 4 }}>
|
||||
{appsSchema.wallets.map((wallet, i) => (
|
||||
<Typography variant="body2" color="grey.400" sx={{ mb: 2 }} key={i}>
|
||||
{appsSchema.wallets.map((wallet) => (
|
||||
<Typography variant="body2" color="grey.400" sx={{ mb: 2 }} key={wallet}>
|
||||
{wallet}
|
||||
</Typography>
|
||||
))}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import React, { ChangeEvent, useState } from 'react';
|
||||
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
|
||||
import { Box, FormControl, FormControlLabel, FormHelperText, Link, Stack, Switch, Typography } from '@mui/material';
|
||||
import { useClientContext } from 'src/context/main';
|
||||
import { ConnectionStatusKind } from 'src/types';
|
||||
import { AppVersion } from 'src/components/AppVersion';
|
||||
|
||||
export const Settings = () => {
|
||||
const { userDefinedGateway, setUserDefinedGateway } = useClientContext();
|
||||
const [gatewayKey, setGatewayKey] = useState<string | undefined>(userDefinedGateway?.gateway);
|
||||
|
||||
const handleIsValidGatewayKey = (isValid: boolean) => {
|
||||
let gateway: string | undefined;
|
||||
|
||||
if (isValid) {
|
||||
gateway = gatewayKey;
|
||||
}
|
||||
|
||||
setUserDefinedGateway((current) => ({ ...current, gateway }));
|
||||
};
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setUserDefinedGateway((current) => ({ ...current, isActive: e.target.checked }));
|
||||
};
|
||||
|
||||
const { connectionStatus } = useClientContext();
|
||||
|
||||
return (
|
||||
<Box height="100%">
|
||||
<Stack justifyContent="space-between" height="100%">
|
||||
<Box>
|
||||
<Typography fontWeight="bold" variant="body2" mb={1}>
|
||||
Select your Gateway
|
||||
</Typography>
|
||||
<Typography color="grey.300" variant="body2" mb={2}>
|
||||
Use a gateway of your choice
|
||||
</Typography>
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={userDefinedGateway?.isActive}
|
||||
onChange={handleChange}
|
||||
disabled={connectionStatus === ConnectionStatusKind.connected}
|
||||
size="small"
|
||||
sx={{ ml: 1 }}
|
||||
/>
|
||||
}
|
||||
label={userDefinedGateway?.isActive ? 'On' : 'Off'}
|
||||
/>
|
||||
{connectionStatus === ConnectionStatusKind.connected && (
|
||||
<FormHelperText sx={{ m: 0, my: 1 }}>This setting is disabled during an active connection</FormHelperText>
|
||||
)}
|
||||
{userDefinedGateway?.isActive && (
|
||||
<IdentityKeyFormField
|
||||
size="small"
|
||||
placeholder="Gateway identity key"
|
||||
onChanged={setGatewayKey}
|
||||
initialValue={gatewayKey}
|
||||
onValidate={handleIsValidGatewayKey}
|
||||
sx={{ mt: 1 }}
|
||||
disabled={connectionStatus === 'connected' || !userDefinedGateway?.isActive}
|
||||
/>
|
||||
)}
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="body2" mb={3}>
|
||||
To find a gateway go to the{' '}
|
||||
<Link
|
||||
underline="none"
|
||||
target="_blank"
|
||||
href="https://explorer.nymtech.net/network-components/gateways"
|
||||
sx={{ cursor: 'pointer' }}
|
||||
color="nym.cta"
|
||||
>
|
||||
Network Explorer
|
||||
</Link>
|
||||
</Typography>
|
||||
<AppVersion />
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Apps, HelpOutline } from '@mui/icons-material';
|
||||
import { Apps, HelpOutline, Settings } from '@mui/icons-material';
|
||||
import { Stack, Link, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { AppVersion } from 'src/components/AppVersion';
|
||||
@@ -7,24 +7,25 @@ import { AppVersion } from 'src/components/AppVersion';
|
||||
const menuSchema = [
|
||||
{ title: 'Supported apps', icon: Apps, path: 'apps' },
|
||||
{ title: 'How to connect guide', icon: HelpOutline, path: 'guide' },
|
||||
{ title: 'Settings', icon: Settings, path: 'settings' },
|
||||
];
|
||||
|
||||
export const Menu = () => {
|
||||
return (
|
||||
<Stack justifyContent="space-between" height="100%">
|
||||
<List dense disablePadding>
|
||||
{menuSchema.map((item) => (
|
||||
<Link component={RouterLink} to={item.path} underline="none" color="white">
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton>
|
||||
<ListItemIcon sx={{ minWidth: 25 }}>{<item.icon sx={{ fontSize: '12px' }} />}</ListItemIcon>{' '}
|
||||
<ListItemText>{item.title}</ListItemText>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</Link>
|
||||
))}
|
||||
</List>
|
||||
<AppVersion />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export const Menu = () => (
|
||||
<Stack justifyContent="space-between" height="100%">
|
||||
<List dense disablePadding>
|
||||
{menuSchema.map((item) => (
|
||||
<Link component={RouterLink} to={item.path} underline="none" color="white" key={item.title}>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton>
|
||||
<ListItemIcon sx={{ minWidth: 25 }}>
|
||||
<item.icon sx={{ fontSize: '12px' }} />
|
||||
</ListItemIcon>{' '}
|
||||
<ListItemText>{item.title}</ListItemText>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</Link>
|
||||
))}
|
||||
</List>
|
||||
<AppVersion />
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import React from 'react';
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import { ConnectionPage } from 'src/pages/connection';
|
||||
import { Menu } from 'src/pages/menu';
|
||||
import { CompatibleApps } from 'src/pages/menu/Apps';
|
||||
import { HelpGuide } from 'src/pages/menu/Guide';
|
||||
import { Settings } from 'src/pages/menu/Settings';
|
||||
|
||||
export const AppRoutes = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route index path="/" element={<ConnectionPage />} />
|
||||
<Route path="menu">
|
||||
<Route index element={<Menu />} />
|
||||
<Route path="apps" element={<CompatibleApps />} />
|
||||
<Route path="guide" element={<HelpGuide />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
export const AppRoutes = () => (
|
||||
<Routes>
|
||||
<Route index path="/" element={<ConnectionPage />} />
|
||||
<Route path="menu">
|
||||
<Route index element={<Menu />} />
|
||||
<Route path="apps" element={<CompatibleApps />} />
|
||||
<Route path="guide" element={<HelpGuide />} />
|
||||
<Route path="settings" element={<Settings />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
);
|
||||
|
||||
@@ -2,12 +2,12 @@ import React from 'react';
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import { Box } from '@mui/material';
|
||||
import { DateTime } from 'luxon';
|
||||
import { AppWindowFrame } from '../components/AppWindowFrame';
|
||||
import { useClientContext } from '../context/main';
|
||||
import { Services } from '../types/directory';
|
||||
import { Disconnected } from 'src/pages/connection/Disconnected';
|
||||
import { Connected } from 'src/pages/connection/Connected';
|
||||
import { ConnectionStatusKind } from 'src/types';
|
||||
import { AppWindowFrame } from '../components/AppWindowFrame';
|
||||
import { useClientContext } from '../context/main';
|
||||
import { Services } from '../types/directory';
|
||||
|
||||
export default {
|
||||
title: 'App/Flow',
|
||||
@@ -87,6 +87,7 @@ export const Mock: ComponentStory<typeof AppWindowFrame> = () => {
|
||||
return (
|
||||
<AppWindowFrame>
|
||||
<Connected
|
||||
clearError={() => {}}
|
||||
gatewayPerformance="Good"
|
||||
showInfoModal={false}
|
||||
closeInfoModal={() => undefined}
|
||||
|
||||
@@ -15,11 +15,10 @@ export default {
|
||||
export const Default: ComponentStory<typeof Connected> = () => (
|
||||
<Box p={2} width={242} sx={{ bgcolor: 'nym.background.dark' }}>
|
||||
<Connected
|
||||
clearError={() => {}}
|
||||
gatewayPerformance="Good"
|
||||
showInfoModal={false}
|
||||
closeInfoModal={() => {
|
||||
return undefined;
|
||||
}}
|
||||
closeInfoModal={() => undefined}
|
||||
status={ConnectionStatusKind.connected}
|
||||
connectedSince={DateTime.now()}
|
||||
ipAddress="127.0.0.1"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import { ConnectionButton } from '../components/ConnectionButton';
|
||||
import { ConnectionStatusKind } from 'src/types';
|
||||
import { ConnectionButton } from '../components/ConnectionButton';
|
||||
|
||||
export default {
|
||||
title: 'Components/ConnectionButton',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import { Box } from '@mui/material';
|
||||
import { ConnectionStatusKind } from '../types';
|
||||
import { Disconnected } from 'src/pages/connection/Disconnected';
|
||||
import { ConnectionStatusKind } from '../types';
|
||||
|
||||
export default {
|
||||
title: 'Layouts/DefaultLayout',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import { PowerButton } from 'src/components/PowerButton';
|
||||
import { PowerButton } from 'src/components/PowerButton/PowerButton';
|
||||
import { ConnectionStatusKind } from 'src/types';
|
||||
|
||||
export default {
|
||||
@@ -13,7 +13,7 @@ export const Disconnected: ComponentStory<typeof PowerButton> = () => (
|
||||
);
|
||||
|
||||
export const Connecting: ComponentStory<typeof PowerButton> = () => (
|
||||
<PowerButton status={ConnectionStatusKind.connecting} />
|
||||
<PowerButton status={ConnectionStatusKind.connecting} disabled />
|
||||
);
|
||||
|
||||
export const Connected: ComponentStory<typeof PowerButton> = () => (
|
||||
@@ -21,9 +21,9 @@ export const Connected: ComponentStory<typeof PowerButton> = () => (
|
||||
);
|
||||
|
||||
export const Disconnecting: ComponentStory<typeof PowerButton> = () => (
|
||||
<PowerButton status={ConnectionStatusKind.disconnecting} />
|
||||
<PowerButton status={ConnectionStatusKind.disconnecting} disabled />
|
||||
);
|
||||
|
||||
export const Disabled: ComponentStory<typeof PowerButton> = () => (
|
||||
<PowerButton status={ConnectionStatusKind.connecting} disabled />
|
||||
<PowerButton status={ConnectionStatusKind.disconnected} disabled />
|
||||
);
|
||||
|
||||
Vendored
+1
@@ -29,6 +29,7 @@ declare module '@mui/material/styles' {
|
||||
*/
|
||||
interface NymPalette {
|
||||
highlight: string;
|
||||
cta: string;
|
||||
success: string;
|
||||
warning: string;
|
||||
info: string;
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
const nymPalette: NymPalette = {
|
||||
/** emphasises important elements */
|
||||
highlight: '#21D072',
|
||||
cta: '#FB6E4E',
|
||||
success: '#21D073',
|
||||
info: '#60D7EF',
|
||||
warning: '#FFE600',
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface UserDefinedGateway {
|
||||
isActive: boolean;
|
||||
gateway?: string;
|
||||
}
|
||||
@@ -2,6 +2,17 @@
|
||||
|
||||
## UNRELEASED
|
||||
|
||||
## [nym-wallet-v1.1.9](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.9) (2023-02-14)
|
||||
|
||||
- Allow more flexibility for user when setting passwords ([#2993])
|
||||
- User feedback on weak passwords ([#2993])
|
||||
- User no longer has to copy mnemonic to continune account creation ([#2948])
|
||||
- Updated instructional steps for creating accounts with a password ([#2962])
|
||||
|
||||
[#2948]: https://github.com/nymtech/nym/issues/2948
|
||||
[#2993]: https://github.com/nymtech/nym/issues/2993
|
||||
[#2962]: https://github.com/nymtech/nym/issues/2962
|
||||
|
||||
## [nym-wallet-v1.1.8](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.8) (2023-01-24)
|
||||
|
||||
- Fix delegations sorting ([#2885])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nymproject/nym-wallet-app",
|
||||
"version": "1.1.8",
|
||||
"version": "1.1.9",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -52,7 +52,8 @@
|
||||
"string-to-color": "^2.2.2",
|
||||
"use-clipboard-copy": "^0.2.0",
|
||||
"uuid": "^8.3.2",
|
||||
"yup": "^0.32.9"
|
||||
"yup": "^0.32.9",
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.15.0",
|
||||
@@ -76,6 +77,7 @@
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"@types/semver": "^7.3.8",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/zxcvbn": "^4.4.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.13.0",
|
||||
"@typescript-eslint/parser": "^5.13.0",
|
||||
"babel-loader": "^8.3.0",
|
||||
@@ -120,4 +122,4 @@
|
||||
"webpack-favicons": "^1.3.8",
|
||||
"webpack-merge": "^5.8.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym_wallet"
|
||||
version = "1.1.8"
|
||||
version = "1.1.9"
|
||||
description = "Nym Native Wallet"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-wallet",
|
||||
"version": "1.1.8"
|
||||
"version": "1.1.9"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
@@ -14,7 +14,13 @@
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"identifier": "net.nymtech.wallet",
|
||||
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"resources": [],
|
||||
"externalBin": [],
|
||||
"copyright": "Copyright © 2021-2022 Nym Technologies SA",
|
||||
@@ -27,7 +33,6 @@
|
||||
"macOS": {
|
||||
"frameworks": [],
|
||||
"minimumSystemVersion": "",
|
||||
|
||||
"exceptionDomain": "",
|
||||
"signingIdentity": "Developer ID Application: Nym Technologies SA (VW5DZLFHM5)",
|
||||
"entitlements": null
|
||||
@@ -40,7 +45,9 @@
|
||||
},
|
||||
"updater": {
|
||||
"active": true,
|
||||
"endpoints": ["https://nymtech.net/.wellknown/wallet/updater.json"],
|
||||
"endpoints": [
|
||||
"https://nymtech.net/.wellknown/wallet/updater.json"
|
||||
],
|
||||
"dialog": true,
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IENCNzQ2M0E5N0VFODE2NApSV1JrZ2U2WE9rYTNETTg1OTBKdE5uWUEra0hML2syOVUvQ2lxZmFZRzZ1T3NWbGM0eVRzUTVhVwo="
|
||||
},
|
||||
@@ -67,4 +74,4 @@
|
||||
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,12 @@ import { SimpleModal } from '../Modals/SimpleModal';
|
||||
import { Warning } from '../Warning';
|
||||
|
||||
const passwordCreationSteps = [
|
||||
'Log out of your wallet',
|
||||
'Log out from the wallet',
|
||||
'Sign in using “Sign in with mnemonic” button',
|
||||
'On the next screen select “Create a password for your account”',
|
||||
'Sign in to the wallet with your new password',
|
||||
'Then come back here to import or create new accounts',
|
||||
'On the next screen select “Create a password"',
|
||||
'Type in the mnemonic you want to create a password for and follow the next steps',
|
||||
'Sign back in the wallet using your new password',
|
||||
'Come back to this page to import or create new accounts',
|
||||
];
|
||||
|
||||
// TODO add the link href value
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
TextField,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { useClipboard } from 'use-clipboard-copy';
|
||||
import { createMnemonic, validateMnemonic } from 'src/requests';
|
||||
import { Console } from 'src/utils/console';
|
||||
import { AccountsContext } from 'src/context';
|
||||
@@ -30,16 +29,16 @@ const importAccountSteps = [
|
||||
];
|
||||
|
||||
const MnemonicStep = ({ mnemonic, onNext, onBack }: { mnemonic: string; onNext: () => void; onBack: () => void }) => {
|
||||
const { copy, copied } = useClipboard({ copiedTimeout: 5000 });
|
||||
const [confirmed, setConfirmed] = useState(false);
|
||||
return (
|
||||
<Box sx={{ mt: 1 }}>
|
||||
<DialogContent>
|
||||
<Mnemonic mnemonic={mnemonic} handleCopy={copy} copied={copied} />
|
||||
<Mnemonic mnemonic={mnemonic} handleConfirmed={setConfirmed} confirmed={confirmed} />
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ p: 3, pt: 0, gap: 2 }}>
|
||||
<StyledBackButton onBack={onBack} />
|
||||
<Button disabled={!copied} fullWidth disableElevation variant="contained" size="large" onClick={onNext}>
|
||||
I saved my mnemonic
|
||||
<Button disabled={!confirmed} fullWidth disableElevation variant="contained" size="large" onClick={onNext}>
|
||||
Continue
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Box>
|
||||
|
||||
@@ -11,15 +11,12 @@ import {
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { AccountsContext } from 'src/context';
|
||||
import { useClipboard } from 'use-clipboard-copy';
|
||||
import { Mnemonic, PasswordInput } from 'src/components';
|
||||
import { StyledBackButton } from 'src/components/StyledBackButton';
|
||||
|
||||
export const MnemonicModal = () => {
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
const { copy, copied } = useClipboard({ copiedTimeout: 5000 });
|
||||
|
||||
const {
|
||||
dialogToDisplay,
|
||||
setDialogToDisplay,
|
||||
@@ -72,7 +69,7 @@ export const MnemonicModal = () => {
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Mnemonic mnemonic={accountMnemonic.value} handleCopy={copy} copied={copied} />
|
||||
<Mnemonic mnemonic={accountMnemonic.value} />
|
||||
)}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,23 +1,32 @@
|
||||
import React from 'react';
|
||||
import { Button, Stack, TextField, Typography } from '@mui/material';
|
||||
import { Check, ContentCopySharp } from '@mui/icons-material';
|
||||
import { Box, Checkbox, FormControlLabel, Stack, TextField, Typography } from '@mui/material';
|
||||
import { Title } from 'src/pages/auth/components/heading';
|
||||
import { Warning } from './Warning';
|
||||
|
||||
export const Mnemonic = ({
|
||||
mnemonic,
|
||||
copied,
|
||||
handleCopy,
|
||||
confirmed,
|
||||
withTitle,
|
||||
handleConfirmed,
|
||||
}: {
|
||||
mnemonic: string;
|
||||
copied: boolean;
|
||||
handleCopy: (text?: string) => void;
|
||||
confirmed?: boolean;
|
||||
withTitle?: boolean;
|
||||
handleConfirmed?: (confirmed: boolean) => void;
|
||||
}) => (
|
||||
<Stack spacing={2} alignItems="center">
|
||||
<Warning>
|
||||
<Typography sx={{ textAlign: 'center' }}>
|
||||
Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the future
|
||||
</Typography>
|
||||
</Warning>
|
||||
<Stack spacing={2}>
|
||||
{withTitle && (
|
||||
<Box sx={{ pb: 2, textAlign: 'center' }}>
|
||||
<Title title="Copy and save or write down your mnemonic" />
|
||||
</Box>
|
||||
)}
|
||||
<Box sx={{ pb: 2 }}>
|
||||
<Warning>
|
||||
<Typography sx={{ textAlign: 'center' }}>
|
||||
Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the future
|
||||
</Typography>
|
||||
</Warning>
|
||||
</Box>
|
||||
<TextField
|
||||
label="Mnemonic"
|
||||
type="input"
|
||||
@@ -38,19 +47,11 @@ export const Mnemonic = ({
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
color="inherit"
|
||||
disableElevation
|
||||
size="large"
|
||||
onClick={() => {
|
||||
handleCopy(mnemonic);
|
||||
}}
|
||||
sx={{
|
||||
width: 250,
|
||||
}}
|
||||
endIcon={!copied ? <ContentCopySharp /> : <Check color="success" />}
|
||||
>
|
||||
Copy mnemonic
|
||||
</Button>
|
||||
{handleConfirmed && (
|
||||
<FormControlLabel
|
||||
label="I saved my mnemonic"
|
||||
control={<Checkbox checked={confirmed} onChange={(_, checked) => handleConfirmed(checked)} />}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -35,7 +35,9 @@ export const inputValidationSchema = Yup.object().shape({
|
||||
.test('Is valid operator cost value', (value, ctx) => {
|
||||
const stringValueToNumber = Math.round(Number(value));
|
||||
|
||||
if (isGreaterThan(stringValueToNumber, -1) && isLessThan(stringValueToNumber, 101)) return true;
|
||||
return ctx.createError({ message: 'Operator cost must be a valid number' });
|
||||
if (isLessThan(stringValueToNumber, 0))
|
||||
return ctx.createError({ message: 'Operator cost must be a valid number' });
|
||||
|
||||
return true;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,52 +1,61 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
import zxcvbn, { ZXCVBNScore } from 'zxcvbn';
|
||||
import { LockOutlined } from '@mui/icons-material';
|
||||
import { LinearProgress, Stack, Typography, Box } from '@mui/material';
|
||||
|
||||
type TStrength = 'weak' | 'medium' | 'strong' | 'init';
|
||||
|
||||
const strong = /^(?=.*[a-z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/;
|
||||
const medium = /^(((?=.*[a-z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[0-9])))(?=.{6,})/;
|
||||
|
||||
const colorMap = {
|
||||
init: 'inherit' as 'inherit',
|
||||
weak: 'error' as 'error',
|
||||
medium: 'warning' as 'warning',
|
||||
strong: 'success' as 'success',
|
||||
4: 'success' as 'success',
|
||||
3: 'success' as 'success',
|
||||
2: 'warning' as 'warning',
|
||||
1: 'error' as 'error',
|
||||
0: 'error' as 'error',
|
||||
};
|
||||
|
||||
const getText = (strength: TStrength) => {
|
||||
switch (strength) {
|
||||
case 'strong':
|
||||
const getText = (score: ZXCVBNScore) => {
|
||||
switch (score) {
|
||||
case 4:
|
||||
return 'Very strong password';
|
||||
case 3:
|
||||
return 'Strong password';
|
||||
case 'medium':
|
||||
return 'Medium strength password';
|
||||
case 'weak':
|
||||
case 2:
|
||||
return 'Average password';
|
||||
case 1:
|
||||
return 'Weak password';
|
||||
case 0:
|
||||
return 'Very weak password';
|
||||
default:
|
||||
return 'Password strength';
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const getTextColor = (strength: TStrength) => {
|
||||
switch (strength) {
|
||||
case 'strong':
|
||||
const getColor = (score: ZXCVBNScore) => {
|
||||
switch (score) {
|
||||
case 4:
|
||||
return 'success.main';
|
||||
case 'medium':
|
||||
case 3:
|
||||
return 'success.main';
|
||||
case 2:
|
||||
return 'warning.main';
|
||||
case 'weak':
|
||||
case 1:
|
||||
return 'error.main';
|
||||
case 0:
|
||||
return 'error.main';
|
||||
default:
|
||||
return 'grey.500';
|
||||
}
|
||||
};
|
||||
|
||||
const getPasswordStrength = (strength: TStrength) => {
|
||||
switch (strength) {
|
||||
case 'strong':
|
||||
const getPasswordStrength = (score: ZXCVBNScore) => {
|
||||
switch (score) {
|
||||
case 4:
|
||||
return 100;
|
||||
case 'medium':
|
||||
case 3:
|
||||
return 75;
|
||||
case 2:
|
||||
return 50;
|
||||
case 1:
|
||||
return 25;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -54,47 +63,32 @@ const getPasswordStrength = (strength: TStrength) => {
|
||||
|
||||
export const PasswordStrength = ({
|
||||
password,
|
||||
onChange,
|
||||
withWarnings,
|
||||
handleIsSafePassword,
|
||||
}: {
|
||||
password: string;
|
||||
onChange: (isStrong: boolean) => void;
|
||||
withWarnings?: boolean;
|
||||
handleIsSafePassword: (isSafe: boolean) => void;
|
||||
}) => {
|
||||
const [strength, setStrength] = useState<TStrength>('init');
|
||||
const result = zxcvbn(password);
|
||||
|
||||
useEffect(() => {
|
||||
if (password.length === 0) {
|
||||
setStrength('init');
|
||||
return;
|
||||
}
|
||||
handleIsSafePassword(result.score > 1);
|
||||
|
||||
if (password.match(strong)) {
|
||||
setStrength('strong');
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.match(medium)) {
|
||||
setStrength('medium');
|
||||
return;
|
||||
}
|
||||
setStrength('weak');
|
||||
}, [password]);
|
||||
|
||||
useEffect(() => {
|
||||
if (strength === 'strong') {
|
||||
onChange(true);
|
||||
} else {
|
||||
onChange(false);
|
||||
}
|
||||
}, [strength]);
|
||||
if (!password.length) return null;
|
||||
|
||||
return (
|
||||
<Stack spacing={0.5}>
|
||||
<LinearProgress variant="determinate" color={colorMap[strength]} value={getPasswordStrength(strength)} />
|
||||
<Box display="flex" alignItems="center">
|
||||
<LockOutlined sx={{ fontSize: 15, color: getTextColor(strength) }} />
|
||||
<Typography variant="caption" sx={{ ml: 0.5, color: getTextColor(strength) }}>
|
||||
{getText(strength)}
|
||||
</Typography>
|
||||
<LinearProgress variant="determinate" color={colorMap[result.score]} value={getPasswordStrength(result.score)} />
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between">
|
||||
<Box display="flex" alignItems="center">
|
||||
<LockOutlined sx={{ fontSize: 15, color: getColor(result.score) }} />
|
||||
<Typography variant="caption" sx={{ ml: 0.5, color: getColor(result.score) }}>
|
||||
{getText(result.score)}
|
||||
</Typography>
|
||||
</Box>
|
||||
{withWarnings && result.feedback.warning && (
|
||||
<Typography variant="caption">{result.feedback.warning}</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import { Stack, TextField } from '@mui/material';
|
||||
import { PasswordStrength } from './password-strength';
|
||||
|
||||
export default {
|
||||
title: 'Wallet / Password Strength',
|
||||
component: PasswordStrength,
|
||||
} as ComponentMeta<typeof PasswordStrength>;
|
||||
|
||||
const Template: ComponentStory<typeof PasswordStrength> = ({ password, withWarnings, handleIsSafePassword }) => {
|
||||
const [value, setValue] = React.useState(password);
|
||||
return (
|
||||
<Stack alignContent="center">
|
||||
<TextField value={value} onChange={(e) => setValue(e.target.value)} sx={{ mb: 0.5 }} />
|
||||
{!!password.length && (
|
||||
<PasswordStrength handleIsSafePassword={handleIsSafePassword} withWarnings={withWarnings} password={password} />
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const VeryStrong = Template.bind({});
|
||||
VeryStrong.args = { password: 'fedgklnrf34£', withWarnings: true, handleIsSafePassword: () => undefined };
|
||||
|
||||
export const Strong = Template.bind({});
|
||||
Strong.args = { password: '"56%abc123?@', withWarnings: true, handleIsSafePassword: () => undefined };
|
||||
|
||||
export const Average = Template.bind({});
|
||||
Average.args = { password: '"abc123?', withWarnings: true, handleIsSafePassword: () => undefined };
|
||||
|
||||
export const Weak = Template.bind({});
|
||||
Weak.args = { password: 'abc123?', withWarnings: true, handleIsSafePassword: () => undefined };
|
||||
|
||||
export const VeryWeak = Template.bind({});
|
||||
VeryWeak.args = {
|
||||
password: 'abc123',
|
||||
withWarnings: true,
|
||||
handleIsSafePassword: () => undefined,
|
||||
};
|
||||
|
||||
export const WithName = Template.bind({});
|
||||
WithName.args = {
|
||||
password: 'fred',
|
||||
withWarnings: true,
|
||||
handleIsSafePassword: () => undefined,
|
||||
};
|
||||
|
||||
export const WithSequence = Template.bind({});
|
||||
WithSequence.args = {
|
||||
password: '121212',
|
||||
withWarnings: true,
|
||||
handleIsSafePassword: () => undefined,
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
password: 'abc123',
|
||||
withWarnings: true,
|
||||
handleIsSafePassword: () => undefined,
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
import React from 'react';
|
||||
import { AuthProvider } from 'src/context';
|
||||
import { AuthRoutes } from 'src/routes/auth';
|
||||
|
||||
export const Auth = () => (
|
||||
<AuthProvider>
|
||||
<AuthRoutes />
|
||||
</AuthProvider>
|
||||
);
|
||||
@@ -9,9 +9,8 @@ import { Subtitle, Title, PasswordStrength } from '../components';
|
||||
|
||||
export const ConnectPassword = () => {
|
||||
const [confirmedPassword, setConfirmedPassword] = useState<string>('');
|
||||
const [isStrongPassword, setIsStrongPassword] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const [isSafePassword, setIsSafePassword] = useState(false);
|
||||
const { mnemonic, password, setPassword, resetState } = useContext(AuthContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -49,7 +48,7 @@ export const ConnectPassword = () => {
|
||||
label="Password"
|
||||
autoFocus
|
||||
/>
|
||||
<PasswordStrength password={password} onChange={(isStrong) => setIsStrongPassword(isStrong)} />
|
||||
<PasswordStrength password={password} handleIsSafePassword={setIsSafePassword} withWarnings />
|
||||
</>
|
||||
<PasswordInput
|
||||
password={confirmedPassword}
|
||||
@@ -59,7 +58,7 @@ export const ConnectPassword = () => {
|
||||
<Button
|
||||
size="large"
|
||||
variant="contained"
|
||||
disabled={password !== confirmedPassword || password.length === 0 || !isStrongPassword || isLoading}
|
||||
disabled={password !== confirmedPassword || password.length === 0 || isLoading || !isSafePassword}
|
||||
onClick={storePassword}
|
||||
>
|
||||
{isLoading ? <CircularProgress size={25} /> : 'Create password'}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Container, Button, Stack } from '@mui/material';
|
||||
import { AuthContext } from 'src/context/auth';
|
||||
import { useClipboard } from 'use-clipboard-copy';
|
||||
import { Mnemonic } from '../../../components';
|
||||
|
||||
export const CreateMnemonic = () => {
|
||||
const { mnemonic, mnemonicWords, generateMnemonic, resetState } = useContext(AuthContext);
|
||||
const navigate = useNavigate();
|
||||
const [confirmed, setConfirmed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (mnemonicWords.length === 0) {
|
||||
@@ -15,12 +15,10 @@ export const CreateMnemonic = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const { copy, copied } = useClipboard({ copiedTimeout: 5000 });
|
||||
return (
|
||||
<Container maxWidth="xs">
|
||||
<Container maxWidth="sm">
|
||||
<Stack alignItems="center" spacing={3} maxWidth="xs">
|
||||
<Mnemonic mnemonic={mnemonic} handleCopy={copy} copied={copied} />
|
||||
|
||||
<Mnemonic mnemonic={mnemonic} handleConfirmed={setConfirmed} confirmed={confirmed} withTitle />
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
@@ -28,9 +26,9 @@ export const CreateMnemonic = () => {
|
||||
size="large"
|
||||
onClick={() => navigate('/verify-mnemonic')}
|
||||
sx={{ width: '100%', fontSize: 15 }}
|
||||
disabled={!copied}
|
||||
disabled={!confirmed}
|
||||
>
|
||||
I saved my mnemonic
|
||||
Continue
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
|
||||
@@ -10,8 +10,8 @@ import { Subtitle, Title, PasswordStrength } from '../components';
|
||||
export const CreatePassword = () => {
|
||||
const { password, setPassword, resetState, mnemonic } = useContext(AuthContext);
|
||||
const [confirmedPassword, setConfirmedPassword] = useState<string>('');
|
||||
const [isStrongPassword, setIsStrongPassword] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isSafePassword, setIsSafePassword] = useState(false);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -48,7 +48,7 @@ export const CreatePassword = () => {
|
||||
label="Password"
|
||||
autoFocus
|
||||
/>
|
||||
<PasswordStrength password={password} onChange={(isStrong) => setIsStrongPassword(isStrong)} />
|
||||
<PasswordStrength password={password} handleIsSafePassword={setIsSafePassword} withWarnings />
|
||||
</>
|
||||
<PasswordInput
|
||||
password={confirmedPassword}
|
||||
@@ -58,7 +58,7 @@ export const CreatePassword = () => {
|
||||
<Button
|
||||
size="large"
|
||||
variant="contained"
|
||||
disabled={password !== confirmedPassword || password.length === 0 || !isStrongPassword || isLoading}
|
||||
disabled={password !== confirmedPassword || password.length === 0 || isLoading || !isSafePassword}
|
||||
onClick={storePassword}
|
||||
>
|
||||
Next
|
||||
|
||||
@@ -17,6 +17,7 @@ import { AppContext, urls } from 'src/context/main';
|
||||
import { isGateway, isMixnode, TBondGatewayArgs, TBondMixNodeArgs, TBondMoreArgs } from 'src/types';
|
||||
import { BondedGateway } from 'src/components/Bonding/BondedGateway';
|
||||
import { RedeemRewardsModal } from 'src/components/Bonding/modals/RedeemRewardsModal';
|
||||
import { Console } from 'src/utils/console';
|
||||
import { BondingContextProvider, useBondingContext } from '../../context';
|
||||
import { getMixnodeStakeSaturation } from '../../requests';
|
||||
|
||||
@@ -104,7 +105,7 @@ const Bonding = () => {
|
||||
}
|
||||
return { isOverSaturated: false, saturationPercentage: undefined };
|
||||
} catch (e) {
|
||||
console.error('Error fetching the saturation, error:', e);
|
||||
Console.error('Error fetching the saturation, error:', e);
|
||||
return { isOverSaturated: false, saturationPercentage: undefined };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ import { CalculateArgs, Inputs } from 'src/components/RewardsPlayground/Inputs';
|
||||
import { TBondedMixnode } from 'src/context';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
||||
import { Console } from 'src/utils/console';
|
||||
import { computeEstimate, computeStakeSaturation, handleCalculatePeriodRewards } from './utils';
|
||||
|
||||
export type DefaultInputValues = {
|
||||
@@ -99,7 +100,7 @@ export const ApyPlayground = ({ bondedNode }: { bondedNode: TBondedMixnode }) =>
|
||||
setStakeSaturation(computedStakeSaturation);
|
||||
setResults(estimationResult);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
Console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
|
||||
const { nextInterval } = await getIntervalAsDate();
|
||||
setIntervalTime(nextInterval);
|
||||
} catch {
|
||||
console.log('cant retrieve next interval');
|
||||
Console.log('cant retrieve next interval');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './auth';
|
||||
export * from './Admin';
|
||||
export * from './balance';
|
||||
export * from './bonding';
|
||||
|
||||
@@ -59,9 +59,5 @@ export const computeMixnodeRewardEstimation = async (args: {
|
||||
totalDelegation: number;
|
||||
profitMarginPercent: string;
|
||||
intervalOperatingCost: { denom: 'unym'; amount: string };
|
||||
}) => {
|
||||
console.log(args);
|
||||
|
||||
return invokeWrapper<RewardEstimationResponse>('compute_mixnode_reward_estimation', args);
|
||||
};
|
||||
}) => invokeWrapper<RewardEstimationResponse>('compute_mixnode_reward_estimation', args);
|
||||
export const getMixnodeUptime = async (mixId: number) => invokeWrapper<number>('get_mixnode_uptime', { mixId });
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.8"
|
||||
version = "1.1.9"
|
||||
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.8"
|
||||
version = "1.1.9"
|
||||
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.8"
|
||||
version = "1.1.9"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -19,7 +19,9 @@ export const IdentityKeyFormField: FCWithChildren<{
|
||||
onValidate?: (isValid: boolean, error?: string) => void;
|
||||
textFieldProps?: TextFieldProps;
|
||||
errorText?: string;
|
||||
size?: 'small' | 'medium';
|
||||
sx?: SxProps;
|
||||
disabled?: boolean;
|
||||
}> = ({
|
||||
required,
|
||||
fullWidth,
|
||||
@@ -33,6 +35,8 @@ export const IdentityKeyFormField: FCWithChildren<{
|
||||
onValidate,
|
||||
textFieldProps,
|
||||
showTickOnValid = true,
|
||||
size,
|
||||
disabled,
|
||||
}) => {
|
||||
const [value, setValue] = React.useState<string | undefined>(initialValue);
|
||||
const [validationError, setValidationError] = React.useState<string | undefined>();
|
||||
@@ -100,6 +104,8 @@ export const IdentityKeyFormField: FCWithChildren<{
|
||||
defaultValue={initialValue}
|
||||
onChange={handleChange}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
size={size}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5338,6 +5338,11 @@
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@types/zxcvbn@^4.4.1":
|
||||
version "4.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/zxcvbn/-/zxcvbn-4.4.1.tgz#46e42cbdcee681b22181478feaf4af2bc4c1abd2"
|
||||
integrity sha512-3NoqvZC2W5gAC5DZbTpCeJ251vGQmgcWIHQJGq2J240HY6ErQ9aWKkwfoKJlHLx+A83WPNTZ9+3cd2ILxbvr1w==
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^5.13.0", "@typescript-eslint/eslint-plugin@^5.7.0":
|
||||
version "5.49.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.49.0.tgz#d0b4556f0792194bf0c2fb297897efa321492389"
|
||||
@@ -19750,3 +19755,8 @@ zwitch@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"
|
||||
integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==
|
||||
|
||||
zxcvbn@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/zxcvbn/-/zxcvbn-4.4.2.tgz#28ec17cf09743edcab056ddd8b1b06262cc73c30"
|
||||
integrity sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ==
|
||||
|
||||
Reference in New Issue
Block a user