Compare commits

..

58 Commits

Author SHA1 Message Date
farbanas 165e7d8b27 Merge branch 'release/v1.1.9' of github.com:nymtech/nym into release/v1.1.9 2023-02-14 07:30:25 -05:00
farbanas 3c97d0d16b Updated changelogs 2023-02-14 07:28:59 -05:00
farbanas bc55c10e19 bumped versions for release/v1.1.9 2023-02-14 07:18:03 -05:00
Fouad a925c39642 Wallet - Fix operator cost in playground (#3021)
* remove upper limit restriction

* update validation
2023-02-14 12:13:19 +00:00
Fouad d23fb366e4 update changelog and fix lint errors (#3023)
add unreleased tag into NC changelog
2023-02-14 10:46:10 +00:00
Bogdan-Ștefan Neacşu 3d500c25c5 Hide coconut runtime flags (#2990) 2023-02-14 11:58:04 +02:00
Fouad 9348722b84 Feature/nym connect pick a gateway (#3008)
* Add new route and initial UI

* allow IdentityKeyFormField to have a small size option

* add disabled prop to the shared IdentityKeyFormField component

* defined custom gateway type

* use custom gateway in state

* set and validate custom gateway in settings page

* validate user gateway when moving away from page

* use storage

* hide gateway input when inactive

* add explorer link to settings page
2023-02-13 22:47:48 +00:00
Fouad 726a406797 update add account instructions (#2981)
* update add account instructions
2023-02-13 10:55:53 +00:00
Fouad 4652d65874 Update password strength checking (#2994)
* use zxcvbn password strength checker

* prevent user from proceeding with a weak password + add tips

* create storybook for password strength component

* update storybook

* update password-strength
2023-02-13 10:27:04 +00:00
Fouad a4ca94ccef dont force user to copy mnemonic (#2992)
* dont force user to copy mnemonic

* add title to mnemonic page
2023-02-13 10:26:37 +00:00
Dave Hrycyszyn 24839770ff Fixing Cargo.lock to include updated version of nym-api 2023-02-08 16:54:09 +00:00
Dave Hrycyszyn 7ac3ec3598 Merge branch 'release/v1.1.9' 2023-02-08 16:21:57 +00:00
Dave Hrycyszyn 77ae71eba4 Changelog bump to trigger nym-connect build 2023-02-08 16:15:33 +00:00
Dave Hrycyszyn d4b836277e Merge branch 'release/v1.1.9' 2023-02-08 16:04:49 +00:00
Dave Hrycyszyn b92ee84874 Building the SDK package and only selected examples 2023-02-08 15:56:30 +00:00
pierre 2eb0ce381a fix(nym-connect): lint errors 2023-02-08 16:36:00 +01:00
Jon Häggblad 9f42f0152b Merge branch 'release/v1.1.9' 2023-02-08 13:27:32 +01:00
Dave Hrycyszyn 5217edcca3 Changelog tweaks 2023-02-08 13:14:12 +01:00
joeiacono2021 e306effdac Merge branch 'release/v1.1.9' of https://github.com/nymtech/nym into release/v1.1.9 2023-02-08 13:14:12 +01:00
joeiacono2021 dc2b1c6d2a changelog changes for release 1.1.9 2023-02-08 13:14:12 +01:00
Jon Häggblad 4232801e80 changelog: add note about fix for unexpected shutdown 2023-02-08 13:14:12 +01:00
Fouad 96df3ad4ce Feature/nym connect health status frontend (#2969)
* filter services on rust side by gateway performance

* format rust code

* create events hook

* use events hook

* remove unused component

remove unused component

update prop names in svg

* display errors in connected screen when needed

update failed health check message
2023-02-08 13:14:12 +01:00
Fouad d614a2b81b link owner field to ng explorer (#2970)
* link owner field to ng explorer
2023-02-08 13:14:12 +01:00
Fouad d27245e184 filter services on rust side by gateway performance (#2966)
* filter services on rust side by gateway performance

* update changelog for NC
2023-02-08 13:14:12 +01:00
Mark Sinclair 5dbfcadfdb GitHub Actions: fix up build-and-upload-binaries-ci.yml 2023-02-08 13:14:12 +01:00
Jędrzej Stuczyński 035dada0e0 introduced '/circulating-supply/total-supply-value' and '/circulating-supply/circulating-supply-value' endpoints (#2965) 2023-02-08 13:14:12 +01:00
Mark Sinclair 1d867156e3 Update build-and-upload-binaries-ci.yml 2023-02-08 13:14:12 +01:00
Mark Sinclair ed9be47ec4 Update build-and-upload-binaries-ci.yml 2023-02-08 13:14:12 +01:00
Mark Sinclair 3aa2e6c54d Update build-and-upload-binaries-ci.yml 2023-02-08 13:14:12 +01:00
Mark Sinclair eb96fc72b9 GitHub Actions: add action to build and upload binaries to CI server 2023-02-08 13:14:12 +01:00
Jon Häggblad 59cec6f03c Don't drop in mixnet connection handlers (#2963) 2023-02-08 13:14:12 +01:00
Fouad c0a0d89a90 NymConnect - Add button animations (#2950)
* add button animations

* pulse and disable button on connecting/disconnecting status

* update button component story

* disabled hover on connecting/disconnecting

* add transition delay

fix up overflow
2023-02-08 13:14:12 +01:00
Jędrzej Stuczyński 362e7f2fea Added an option to set custom 'host' for the native client (#2939)
* Added an option to set custom 'host' for the native client

* Changelog entry
2023-02-08 08:59:48 +01:00
Pierre Dommerc eeba17a01f build(nc-android): prepare for apk release (#2943)
* chore(nc-android): prepare for production build

* refactor(nc-android): remove dead code

* feat(nc-android): update native color theme

* feat(nc-android): update native color theme

* build(nc-android): fix rfd version issue

* build(nc-android): fix dist dir no such file error

* fix(nc-android): post rebase changes
2023-02-08 08:59:48 +01:00
Bogdan-Ștefan Neacşu 3bc7f281b4 Fix typo during merge back to develop (#2956) 2023-02-08 08:59:48 +01:00
cgi-bin/ 5a89e894a9 typo: electrum (#2954) 2023-02-08 08:59:48 +01:00
farbanas 8dbddb7b7e fix: formatting 2023-02-08 08:59:48 +01:00
farbanas b62c969a7c fix: wrong parameter type for addresses in generator commands (should be AccountId instead of String) 2023-02-08 08:59:48 +01:00
Fran Arbanas 5d10e62450 Update CHANGELOG.md 2023-02-08 08:59:48 +01:00
Jon Häggblad 21e636616d Merge tag 'nym-binaries-v1.1.8' into develop 2023-02-08 08:25:24 +01:00
farbanas 9881a94757 bumped nym-api version 2023-02-03 15:12:18 +01:00
Jędrzej Stuczyński 76b07d487b introduced '/circulating-supply/total-supply-value' and '/circulating-supply/circulating-supply-value' endpoints (#2965) 2023-02-03 13:37:55 +00:00
farbanas f04fc452dc Merge branch 'release/v1.1.8' of github.com:nymtech/nym into release/v1.1.8 2023-01-31 14:05:53 +01:00
farbanas be90d03129 changelog cleanup 2023-01-31 14:01:25 +01:00
Bogdan-Ștefan Neacşu 0a3e42700c Fix vote soft error everywhere (#2941) 2023-01-31 14:58:47 +02:00
joeiacono2021 55d554701c Merge branch 'release/v1.1.8' of https://github.com/nymtech/nym into release/v1.1.8 2023-01-31 12:55:09 +00:00
joeiacono2021 19c4769260 Changelog Updates for RELEASE 1.1.8 on 31/01 2023-01-31 12:54:55 +00:00
farbanas 71aadc8e1b update versions for the release v1.1.8 2023-01-31 13:44:41 +01:00
Fouad 95340b5817 Feature/nym connect new UI (#2916)
* reduce window size

* use new highlight color

* use react router

* render new routes

* remove old help page

* render app routes

* update connection status UI

* remove service provider info

* remove unneeded additional step

* render title from route

* experimental warning as component

* render connection page

* nym-connect: connectivity status improvements (#2915)

* connect: keep track of connectivity state

* nym-connect: query connection state

* nym-connect: function for kicking of the health check task

* rustfmt

* nym-connect: extract out into function

* nym-connect: extract out events.rs

* add app version to menu page

* help page content and style updates

* update guide content

* use layout component on disconnect page

* handle gateway issues

* only show info modal once after connecting

* power button colors

* update stories and button colors

---------

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
2023-01-31 11:39:38 +00:00
Tommy Verrall 12751665bb white space 2023-01-31 09:14:56 -01:00
Tommy Verrall 01b86bcc0d updating qwerty contract addresses 2023-01-31 07:39:18 -01:00
Bogdan-Ștefan Neacşu c6ce8caaf7 Feature/fix soft multisig error (#2938) 2023-01-30 18:44:45 +02:00
Jędrzej Stuczyński 265713b9d2 Renamed 'initial_supply' to 'total_supply' in the 'circulating-supply' endpoint (#2932)
* Renamed 'initial_supply' to 'total_supply' in the 'circulating-supply' endpoint

* clippy issue messing with CI
2023-01-30 15:39:55 +00:00
Jon Häggblad c9af4721f3 wasm-utils: fix clippy 2023-01-30 10:46:11 +01:00
Pierre Dommerc 8c0ab7c697 feat(explorer): add routing score on gateway list (#2913)
* feat: adding routing score on gateway list

* feat(explorer): adding routing score on gateway list

* feat(explorer): add routing score on gateway list
2023-01-26 18:39:26 +01:00
Bogdan-Ștefan Neacșu 92b220ca4b Fix typo 2023-01-26 12:27:11 +02:00
Jędrzej Stuczyński c218cba96c Feature/default nym api (#2898)
* Setting default 'id' if not provided

* Modified 'NymConfig' to always require 'id'

* moved creation of nym-api directories away from 'override_config'

* missing optional id usage in nym-connect

* changelog

* Removed default value for '--id' argument
2023-01-25 15:49:28 +01:00
Jędrzej Stuczyński c958975fff Merge branch 'master' into release/v1.1.8 2023-01-25 13:40:20 +00:00
92 changed files with 1111 additions and 1027 deletions
@@ -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
View File
@@ -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
View File
@@ -3438,7 +3438,7 @@ dependencies = [
[[package]]
name = "nym-api"
version = "1.1.8"
version = "1.1.9"
dependencies = [
"anyhow",
"async-trait",
+1 -1
View File
@@ -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 -1
View File
@@ -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"
+2 -2
View File
@@ -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
+2 -2
View 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 -1
View File
@@ -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"
+2 -2
View File
@@ -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
+2 -2
View 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 -1
View File
@@ -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
+10
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nym/network-explorer",
"version": "1.0.4",
"version": "1.0.5",
"private": true,
"license": "Apache-2.0",
"dependencies": {
+11 -1
View File
@@ -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
View File
@@ -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>",
+3 -2
View File
@@ -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
+3 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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>",
+5 -1
View File
@@ -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.
+59 -4
View File
@@ -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(),
)))
}
+7 -2
View File
@@ -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>,
}
+14
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nym/nym-connect",
"version": "1.1.8",
"version": "1.1.9",
"main": "index.js",
"license": "MIT",
"scripts": {
+3 -1
View File
@@ -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"}
+1
View File
@@ -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 -1
View File
@@ -1,7 +1,7 @@
{
"package": {
"productName": "nym-connect",
"version": "1.1.8"
"version": "1.1.9"
},
"build": {
"distDir": "../dist",
-74
View File
@@ -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,
},
]}
/>
);
};
+20 -2
View File
@@ -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>
);
};
+16 -14
View File
@@ -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';
-132
View File
@@ -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;
}
}
+52 -85
View File
@@ -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,
],
);
+2
View File
@@ -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<{
+68
View File
@@ -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());
};
}, []);
};
+2 -3
View File
@@ -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');
+42 -37
View File
@@ -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}
+4 -4
View File
@@ -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>
))}
+85
View File
@@ -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>
);
};
+21 -20
View File
@@ -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>
);
+13 -12
View File
@@ -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>
);
+4 -3
View File
@@ -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 />
);
+1
View File
@@ -29,6 +29,7 @@ declare module '@mui/material/styles' {
*/
interface NymPalette {
highlight: string;
cta: string;
success: string;
warning: string;
info: string;
+1
View File
@@ -21,6 +21,7 @@ import {
const nymPalette: NymPalette = {
/** emphasises important elements */
highlight: '#21D072',
cta: '#FB6E4E',
success: '#21D073',
info: '#60D7EF',
warning: '#FFE600',
+4
View File
@@ -0,0 +1,4 @@
export interface UserDefinedGateway {
isActive: boolean;
gateway?: string;
}
+11
View File
@@ -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])
+5 -3
View File
@@ -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 -1
View File
@@ -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 = ""
+12 -5
View File
@@ -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>
+27 -26
View File
@@ -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,
};
-9
View File
@@ -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
+2 -1
View File
@@ -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);
}
};
@@ -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
View File
@@ -1,4 +1,3 @@
export * from './auth';
export * from './Admin';
export * from './balance';
export * from './bonding';
+1 -5
View File
@@ -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
-15
View File
@@ -1,15 +0,0 @@
## Binary init checker
### WIP
A simple tool to ensure that all binaries init with the correct format, using the assert.sh library
Simply run `./build_and_run.sh $GIT_BRANCH`
This will run through all the binaries anc check the fields that we expect to be initialised when passing the parameters into nyms core binaries
## TODO
- Introduce all binaries
- Sort paths and locations
- Tidy up
- Run in CI
-186
View File
@@ -1,186 +0,0 @@
#!/bin/bash
# assert.sh 1.1 - bash unit testing framework
# Copyright (C) 2009-2015 Robert Lehmann
#
# http://github.com/lehmannro/assert.sh
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
export DISCOVERONLY=${DISCOVERONLY:-}
export DEBUG=${DEBUG:-}
export STOP=${STOP:-}
export INVARIANT=${INVARIANT:-}
export CONTINUE=${CONTINUE:-}
args="$(getopt -n "$0" -l \
verbose,help,stop,discover,invariant,continue vhxdic $*)" \
|| exit -1
for arg in $args; do
case "$arg" in
-h)
echo "$0 [-vxidc]" \
"[--verbose] [--stop] [--invariant] [--discover] [--continue]"
echo "`sed 's/./ /g' <<< "$0"` [-h] [--help]"
exit 0;;
--help)
cat <<EOF
Usage: $0 [options]
Language-agnostic unit tests for subprocesses.
Options:
-v, --verbose generate output for every individual test case
-x, --stop stop running tests after the first failure
-i, --invariant do not measure timings to remain invariant between runs
-d, --discover collect test suites only, do not run any tests
-c, --continue do not modify exit code to test suite status
-h show brief usage information and exit
--help show this help message and exit
EOF
exit 0;;
-v|--verbose)
DEBUG=1;;
-x|--stop)
STOP=1;;
-i|--invariant)
INVARIANT=1;;
-d|--discover)
DISCOVERONLY=1;;
-c|--continue)
CONTINUE=1;;
esac
done
_indent=$'\n\t' # local format helper
_assert_reset() {
tests_ran=0
tests_failed=0
tests_errors=()
tests_starttime="$(date +%s%N)" # nanoseconds_since_epoch
}
assert_end() {
# assert_end [suite ..]
tests_endtime="$(date +%s%N)"
# required visible decimal place for seconds (leading zeros if needed)
local tests_time="$( \
printf "%010d" "$(( ${tests_endtime/%N/000000000}
- ${tests_starttime/%N/000000000} ))")" # in ns
tests="$tests_ran ${*:+$* }tests"
[[ -n "$DISCOVERONLY" ]] && echo "collected $tests." && _assert_reset && return
[[ -n "$DEBUG" ]] && echo
# to get report_time split tests_time on 2 substrings:
# ${tests_time:0:${#tests_time}-9} - seconds
# ${tests_time:${#tests_time}-9:3} - milliseconds
[[ -z "$INVARIANT" ]] \
&& report_time=" in ${tests_time:0:${#tests_time}-9}.${tests_time:${#tests_time}-9:3}s" \
|| report_time=
if [[ "$tests_failed" -eq 0 ]]; then
echo "all $tests passed$report_time."
else
for error in "${tests_errors[@]}"; do echo "$error"; done
echo "$tests_failed of $tests failed$report_time."
fi
tests_failed_previous=$tests_failed
[[ $tests_failed -gt 0 ]] && tests_suite_status=1
_assert_reset
}
assert() {
# assert <command> <expected stdout> [stdin]
(( tests_ran++ )) || :
[[ -z "$DISCOVERONLY" ]] || return
expected=$(echo -ne "${2:-}")
result="$(eval 2>/dev/null $1 <<< ${3:-})" || true
if [[ "$result" == "$expected" ]]; then
[[ -z "$DEBUG" ]] || echo -n .
return
fi
result="$(sed -e :a -e '$!N;s/\n/\\n/;ta' <<< "$result")"
[[ -z "$result" ]] && result="nothing" || result="\"$result\""
[[ -z "$2" ]] && expected="nothing" || expected="\"$2\""
_assert_fail "expected $expected${_indent}got $result" "$1" "$3"
}
assert_raises() {
# assert_raises <command> <expected code> [stdin]
(( tests_ran++ )) || :
[[ -z "$DISCOVERONLY" ]] || return
status=0
(eval $1 <<< ${3:-}) > /dev/null 2>&1 || status=$?
expected=${2:-0}
if [[ "$status" -eq "$expected" ]]; then
[[ -z "$DEBUG" ]] || echo -n .
return
fi
_assert_fail "program terminated with code $status instead of $expected" "$1" "$3"
}
_assert_fail() {
# _assert_fail <failure> <command> <stdin>
[[ -n "$DEBUG" ]] && echo -n X
report="test #$tests_ran \"$2${3:+ <<< $3}\" failed:${_indent}$1"
if [[ -n "$STOP" ]]; then
[[ -n "$DEBUG" ]] && echo
echo "$report"
exit 1
fi
tests_errors[$tests_failed]="$report"
(( tests_failed++ )) || :
}
skip_if() {
# skip_if <command ..>
(eval $@) > /dev/null 2>&1 && status=0 || status=$?
[[ "$status" -eq 0 ]] || return
skip
}
skip() {
# skip (no arguments)
shopt -q extdebug && tests_extdebug=0 || tests_extdebug=1
shopt -q -o errexit && tests_errexit=0 || tests_errexit=1
# enable extdebug so returning 1 in a DEBUG trap handler skips next command
shopt -s extdebug
# disable errexit (set -e) so we can safely return 1 without causing exit
set +o errexit
tests_trapped=0
trap _skip DEBUG
}
_skip() {
if [[ $tests_trapped -eq 0 ]]; then
# DEBUG trap for command we want to skip. Do not remove the handler
# yet because *after* the command we need to reset extdebug/errexit (in
# another DEBUG trap.)
tests_trapped=1
[[ -z "$DEBUG" ]] || echo -n s
return 1
else
trap - DEBUG
[[ $tests_extdebug -eq 0 ]] || shopt -u extdebug
[[ $tests_errexit -eq 1 ]] || set -o errexit
return 0
fi
}
_assert_reset
: ${tests_suite_status:=0} # remember if any of the tests failed so far
_assert_cleanup() {
local status=$?
# modify exit code if it's not already non-zero
[[ $status -eq 0 && -z $CONTINUE ]] && exit $tests_suite_status
}
trap _assert_cleanup EXIT
-28
View File
@@ -1,28 +0,0 @@
#!/bin/bash
PWD="../"
GIT_BRANCH=$1
VERSION_NUMBER=$2
# lets make sure the branch is up to date
# ---------------------------------------
git checkout develop
git fetch origin
git checkout $GIT_BRANCH
git pull origin $GIT_BRANCH
# ---------------------------------------
echo "working directory ${PWD}"
#build all binaries...
#expect the cargo tool chain to be installed on the machine
cargo build --release --all
#here there should be the applicable binaries to test inits
./nym-mixnode-binary-check.sh "$VERSION_NUMBER"
sleep 2
echo "running gateway binary check"
#./nym-gateway-binary-check.sh "$VERSION_NUMBER"
-74
View File
@@ -1,74 +0,0 @@
#!/bin/bash
set -e
. assert.sh -v -x
PWD="../"
RELEASE_DIRECTORY="target/release"
VERSION_NUMBER=$1
WALLET_ADDRESS_CONST=n1435n84se65tn7yv536am0sfvng4yyrwj7thhxr
MOCK_HOST="1.2.3.4"
OUTPUT=$(for i in {1..8}; do echo -n $(($RANDOM % 10)); done)
ID="test-${OUTPUT}"
BINARY_NAME="nym-gateway"
echo "the version number is ${VERSION_NUMBER} to be installed from github"
cd ${PWD}${RELEASE_DIRECTORY}
# steps
# we curl the existing binary from the release page of github
# we init the binary to check successful init
# then in our testing branch, we build the binary locally
# re run the init based upon the configuration injected
# we validate that no errors are return from upgrading the binary against the test
# install the current release binary
# so this is dependant on running on a linux machine for the time being
curl -L https://github.com/nymtech/nym/releases/download/nym-binaries-${RELEASE_VERSION_NUMBER}/$BINARY_NAME -o $BINARY_NAME
chmod u+x $BINARY_NAME
#--------------------------------------
# functions
#--------------------------------------
check_gateway_binary_build() if [ -f nym-gateway ]; then
echo "running init tests"
# we wont use config env files for now
# unless we want to use a specific environment
OUTPUT=$(./nym-gateway --output json init --id ${ID} --host ${MOCK_HOST} --wallet-address ${WALLET_ADDRESS_CONST}) >/dev/null 2>&1
# get jq values for things we can assert against
VALUE=$(echo ${OUTPUT} | jq .wallet_address)
VALUE=${VALUE#\"}
VALUE=${VALUE%\"}
echo $OUTPUT
sleep 2
# do asserts here based upon the output on init
assert $(echo ${VALUE}) $(echo ${WALLET_ADDRESS_CONST})
assert_end nym-gateway-tests
else
echo "exting test no binary found"
fi
# we run the release version first
check_gateway_binary_build
# lets remove the binary then navigate to the target/release directory for checking the latest version
# expect to have successful output and configuration
if [ -f $BINARY_NAME ]; then
echo "removing nym-mixnode"
rm -rf $BINARY_NAME
else
echo "no binary found exiting"
exit 1
fi
# we should expect it to pass because no errors should be presented when performing the upgrade of an init
# this should be caught at testing staage
check_gateway_binary_build
-89
View File
@@ -1,89 +0,0 @@
#!/bin/bash
. assert.sh -v -x
PWD="../"
RELEASE_DIRECTORY="target/release"
RELEASE_VERSION_NUMBER=$1
WALLET_ADDRESS_CONST=n1435n84se65tn7yv536am0sfvng4yyrwj7thhxr
MOCK_HOST="1.2.3.4"
OUTPUT=$(for i in {1..8}; do echo -n $(($RANDOM % 10)); done)
ID="test-${OUTPUT}"
BINARY_NAME="nym-mixnode"
echo "the version number is ${VERSION_NUMBER} to be installed from github"
# steps
# we curl the existing binary from the release page of github
# we init the binary to check successful init
# then in our testing branch, we build the binary locally
# re run the init based upon the configuration injected
# we validate that no errors are return from upgrading the binary against the test
# install the current release binary
# so this is dependant on running on a linux machine for the time being
curl -L "https://github.com/nymtech/nym/releases/download/nym-binaries-${RELEASE_VERSION_NUMBER}/${BINARY_NAME}" -o nym-mixnode
chmod u+x "$BINARY_NAME"
#--------------------------------------
# functions
#--------------------------------------
check_mixnode_binary_build() {
if [ -f "$BINARY_NAME" ]; then
echo "running init tests"
# we wont use config env files for now
OUTPUT=$(./${BINARY_NAME} --output json init --id ${ID} --host ${MOCK_HOST} --wallet-address ${WALLET_ADDRESS_CONST} > /dev/null)
# get jq values for things we can assert against
# tidy this bit up - okay for first push
VALUE="$(echo ${OUTPUT} | jq .wallet_address | tr -d '"')"
# do asserts here based upon the output on init
assert "echo ${VALUE}" $(echo ${WALLET_ADDRESS_CONST})
assert_end nym-mixnode-tests
else
echo "exiting test no binary found"
fi
}
#-------------------------------
# tests
#-------------------------------
# we run the release version first
check_mixnode_binary_build
# whoami
# this is dependant on where it runs on ci potentially, will need to tweak this in the future
first_init=$(cat /root/.nym/mixnodes/${ID}/config/config.toml | grep -v "^\[mixnode\]$" | grep -v "^version =")
#lets remove the binary then navigate to the target/release directory for checking the latest version
if [ -f "$BINARY_NAME" ]; then
echo "removing nym-mixnode"
rm -rf "$BINARY_NAME"
echo "successfully removed nym-mixnode"
else
echo "no binary found exiting"
exit 1
fi
# we should expect it to pass because no errors should be presented when performing the upgrade of an init
# this should be caught at testing stage
# navigate to latest binary build
cd $PWD$RELEASE_DIRECTORY
#re run against the current binary built locally
check_mixnode_binary_build
echo "diff the config files after each init"
echo "-------------------------------------"
second_init=$(cat /root/.nym/mixnodes/${ID}/config/config.toml | grep -v "^\[mixnode\]$" | grep -v "^version =")
diff -w <(echo "$first_init") <(echo "$second_init")
# check the status of the diff
if [ $? -eq 0 ]; then
echo "no differences in config files, exiting script"
exit 0
else
echo "there are differences in the config files, it may require a fresh init on the binary"
exit 1
fi
-2
View File
@@ -1,2 +0,0 @@
#!/bin/bash
# TODO
+1 -1
View File
@@ -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}
/>
);
};
+10
View File
@@ -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==