Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa833310da | |||
| 17ed80018f | |||
| cdfcb065b7 | |||
| fd1c0ae62b | |||
| bd7399091b | |||
| 00fad44e2e | |||
| ea33c332ee | |||
| b39f8af8d0 | |||
| a400463b7e | |||
| 82928af64c | |||
| c5891f546c | |||
| 580b677489 | |||
| 2093789c5c | |||
| d09864b99f | |||
| f6e8278592 | |||
| b085a8a636 | |||
| e6ce531aeb | |||
| 1a860eb3f5 | |||
| 09cfd9ff05 | |||
| 2c88ca137a | |||
| ca3bfc859a | |||
| 529db99b8e | |||
| fd4f083742 | |||
| 06256e512d | |||
| 6b4e915cd6 | |||
| 19cdb7f629 | |||
| 029f8904fe | |||
| 5f852e04a0 | |||
| 365b955c90 | |||
| f14a87809e | |||
| a18eec2fe5 | |||
| 3c99644420 | |||
| d6e6369389 | |||
| eff1f383c3 | |||
| a192e0d745 | |||
| 6168ac9e9b | |||
| f7d96042de | |||
| 353ccbe258 | |||
| 6b74a1f091 | |||
| 2f191f50ee | |||
| 0de1196f85 | |||
| 78c1cc05e1 | |||
| ee8101db04 | |||
| cd6c68907c | |||
| 452e9ed637 | |||
| eeeb8052b6 | |||
| bc79ee0cad | |||
| aa5cbd06d4 | |||
| 15cdf579b2 | |||
| caf9429431 | |||
| 708782fdf9 | |||
| 3fc11d8bef | |||
| 2eadda2295 | |||
| 9434cb266b | |||
| 8f49db1150 | |||
| c45e8da43d | |||
| 12cc49a734 | |||
| 7e56a9e88c | |||
| 9790009eac | |||
| 379d593daf | |||
| ce75b99b6f | |||
| bcb7c41fd7 | |||
| bb091ce47f | |||
| effed4d7d6 | |||
| d480ddb133 | |||
| b119820591 | |||
| e128949dc2 | |||
| 9499b987e5 | |||
| d6ac786295 | |||
| 4d09d9c3db | |||
| 8c9044adf3 | |||
| 472085ca52 | |||
| 2f089e80ff |
@@ -34,7 +34,7 @@ on:
|
||||
- 'tools/ts-rs-cli/**'
|
||||
|
||||
env:
|
||||
NETWORK: mainnet
|
||||
NETWORK: mainnet
|
||||
|
||||
jobs:
|
||||
publish-nym:
|
||||
@@ -47,15 +47,6 @@ jobs:
|
||||
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
|
||||
@@ -84,29 +75,31 @@ jobs:
|
||||
|
||||
- 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
|
||||
rm -rf ci-builds || true
|
||||
mkdir ci-builds
|
||||
|
||||
cp contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm $OUTPUT_DIR
|
||||
cp target/release/nym-client ci-builds
|
||||
cp target/release/nym-gateway ci-builds
|
||||
cp target/release/nym-mixnode ci-builds
|
||||
cp target/release/nym-socks5-client ci-builds
|
||||
cp target/release/nym-api ci-builds
|
||||
cp target/release/nym-network-requester ci-builds
|
||||
cp target/release/nym-network-statistics ci-builds
|
||||
cp target/release/nym-cli ci-builds
|
||||
|
||||
cp contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm ci-builds
|
||||
cp contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm ci-builds
|
||||
|
||||
- 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"
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "ci-builds/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/${{ env.GITHUB_REF_NAME }}/
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
name: Tests for validator API
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "nym-api/tests/**"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: nym-api/tests
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: validator api tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Node v18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.1.0
|
||||
|
||||
- name: Install yarn
|
||||
run: yarn install
|
||||
|
||||
- name: Run yarn
|
||||
run: yarn
|
||||
|
||||
- name: Launch tests
|
||||
run: yarn test:qa
|
||||
working-directory: nym-api/tests
|
||||
+4
-11
@@ -4,20 +4,13 @@ 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])
|
||||
|
||||
### 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])
|
||||
- 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.
|
||||
|
||||
[#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
|
||||
[#2890]: https://github.com/nymtech/nym/pull/2890
|
||||
[#2939]: https://github.com/nymtech/nym/pull/2939
|
||||
|
||||
# [v1.1.8] (2023-01-31)
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -3438,7 +3438,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.9"
|
||||
version = "1.1.8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "client-core"
|
||||
version = "1.1.9"
|
||||
version = "1.1.8"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.66"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.9"
|
||||
version = "1.1.8"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -31,7 +31,7 @@ pub(crate) struct Init {
|
||||
force_register_gateway: bool,
|
||||
|
||||
/// Comma separated list of rest endpoints of the nyxd validators
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',', hide = true)]
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',')]
|
||||
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, hide = true)]
|
||||
#[clap(long)]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
|
||||
/// Save a summary of the initialization to a json file
|
||||
|
||||
@@ -23,7 +23,7 @@ pub(crate) struct Run {
|
||||
id: String,
|
||||
|
||||
/// Comma separated list of rest endpoints of the nyxd validators
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',', hide = true)]
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',')]
|
||||
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, hide = true)]
|
||||
#[clap(long)]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.9"
|
||||
version = "1.1.8"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -43,7 +43,7 @@ pub(crate) struct Init {
|
||||
force_register_gateway: bool,
|
||||
|
||||
/// Comma separated list of rest endpoints of the nyxd validators
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',', hide = true)]
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',')]
|
||||
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, hide = true)]
|
||||
#[clap(long)]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
|
||||
/// Save a summary of the initialization to a json file
|
||||
|
||||
@@ -43,7 +43,7 @@ pub(crate) struct Run {
|
||||
gateway: Option<identity::PublicKey>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the nyxd validators
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',', hide = true)]
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',')]
|
||||
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, hide = true)]
|
||||
#[clap(long)]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.1.9"
|
||||
version = "1.1.8"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
## UNRELEASED
|
||||
|
||||
- nothing yet
|
||||
|
||||
## [nym-explorer-v1.0.5](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.5) (2023-02-14)
|
||||
|
||||
- NE - link `Owner` field on the node detail page to the account details on NG explorer ([#2923])
|
||||
- NE - Upgrade Sandbox and make below changes: ([#2332])
|
||||
|
||||
[#2923]: https://github.com/nymtech/nym/issues/2923
|
||||
[#2332]: https://github.com/nymtech/nym/issues/2332
|
||||
|
||||
## [nym-explorer-v1.0.4](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.4) (2023-01-31)
|
||||
|
||||
- Add routing score on gateway list ([#2913])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nym/network-explorer",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.4",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Link, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
|
||||
import { 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,19 +37,9 @@ function formatCellValues(val: string | number, field: string) {
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (field === 'bond') {
|
||||
return unymToNym(val, 6);
|
||||
}
|
||||
|
||||
if (field === 'owner') {
|
||||
return (
|
||||
<Link underline="none" color="inherit" target="_blank" href={`https://mixnet.explorers.guru/account/${val}`}>
|
||||
{val}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-gateway"
|
||||
version = "1.1.9"
|
||||
version = "1.1.8"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
@@ -55,8 +55,7 @@ pub struct Init {
|
||||
long,
|
||||
alias = "validators",
|
||||
alias = "nymd_validators",
|
||||
value_delimiter = ',',
|
||||
hide = true
|
||||
value_delimiter = ','
|
||||
)]
|
||||
// the alias here is included for backwards compatibility (1.1.4 and before)
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
@@ -67,7 +66,7 @@ pub struct Init {
|
||||
|
||||
/// Set this gateway to work only with coconut credentials; that would disallow clients to
|
||||
/// bypass bandwidth credential requirement
|
||||
#[clap(long, hide = true)]
|
||||
#[clap(long)]
|
||||
only_coconut_credentials: Option<bool>,
|
||||
|
||||
/// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server
|
||||
|
||||
@@ -53,8 +53,7 @@ pub struct Run {
|
||||
long,
|
||||
alias = "validators",
|
||||
alias = "nymd_validators",
|
||||
value_delimiter = ',',
|
||||
hide = true
|
||||
value_delimiter = ','
|
||||
)]
|
||||
// the alias here is included for backwards compatibility (1.1.4 and before)
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
@@ -65,7 +64,7 @@ pub struct Run {
|
||||
|
||||
/// Set this gateway to work only with coconut credentials; that would disallow clients to
|
||||
/// bypass bandwidth credential requirement
|
||||
#[clap(long, hide = true)]
|
||||
#[clap(long)]
|
||||
only_coconut_credentials: Option<bool>,
|
||||
|
||||
/// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server
|
||||
|
||||
@@ -181,7 +181,6 @@ 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! {
|
||||
|
||||
+2
-3
@@ -4,8 +4,7 @@
|
||||
"nym-wallet",
|
||||
"nym-connect",
|
||||
"nym-connect-android",
|
||||
"sdk/typescript/examples/docs",
|
||||
"sdk/typescript/packages/**"
|
||||
"sdk/typescript/**"
|
||||
],
|
||||
"version": "0.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-mixnode"
|
||||
version = "1.1.10"
|
||||
version = "1.1.9"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
@@ -77,7 +77,6 @@ impl ConnectionHandler {
|
||||
mut shutdown: TaskClient,
|
||||
) {
|
||||
debug!("Starting connection handler for {:?}", remote);
|
||||
shutdown.mark_as_success();
|
||||
let mut framed_conn = Framed::new(conn, SphinxCodec);
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-api"
|
||||
version = "1.1.10"
|
||||
version = "1.1.8"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
@@ -15,11 +15,7 @@ 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_full_circulating_supply,
|
||||
routes::get_total_supply,
|
||||
routes::get_circulating_supply
|
||||
]
|
||||
openapi_get_routes_spec![settings: routes::get_circulating_supply]
|
||||
}
|
||||
|
||||
/// Spawn the circulating supply cache refresher.
|
||||
|
||||
@@ -1,30 +1,15 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use rocket::http::Status;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
|
||||
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_full_circulating_supply(
|
||||
pub(crate) async fn get_circulating_supply(
|
||||
cache: &State<CirculatingSupplyCache>,
|
||||
) -> Result<Json<CirculatingSupplyResponse>, ErrorResponse> {
|
||||
match cache.get_circulating_supply().await {
|
||||
@@ -35,43 +20,3 @@ pub(crate) async fn get_full_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(),
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ pub(crate) mod tests {
|
||||
use coconut_dkg_common::dealer::DealerDetails;
|
||||
use cosmwasm_std::Addr;
|
||||
use dkg::bte::keys::KeyPair as DkgKeyPair;
|
||||
use dkg::bte::Params;
|
||||
use dkg::bte::{Params, PublicKeyWithProof};
|
||||
use rand::rngs::OsRng;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
@@ -186,8 +186,18 @@ pub(crate) mod tests {
|
||||
let mut bytes = bs58::decode(details.bte_public_key_with_proof.clone())
|
||||
.into_vec()
|
||||
.unwrap();
|
||||
let last_byte = bytes.last_mut().unwrap();
|
||||
*last_byte += 1;
|
||||
// Find another value for last byte that still deserializes to a public key with proof
|
||||
let initial_byte = *bytes.last_mut().unwrap();
|
||||
loop {
|
||||
let last_byte = bytes.last_mut().unwrap();
|
||||
let (ret, _) = last_byte.overflowing_add(1);
|
||||
*last_byte = ret;
|
||||
// stop when we find that value, or if we do a full round trip of u8 values
|
||||
// and can't find one, in which case this test is invalid
|
||||
if PublicKeyWithProof::try_from_bytes(&bytes).is_ok() || ret == initial_byte {
|
||||
break;
|
||||
}
|
||||
}
|
||||
details.bte_public_key_with_proof = bs58::encode(&bytes).into_string();
|
||||
});
|
||||
|
||||
|
||||
@@ -93,16 +93,11 @@ pub(crate) struct CliArgs {
|
||||
pub(crate) enabled_credentials_mode: Option<bool>,
|
||||
|
||||
/// Announced address where coconut clients will connect.
|
||||
#[clap(long, hide = true)]
|
||||
#[clap(long)]
|
||||
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",
|
||||
hide = true
|
||||
)]
|
||||
#[clap(long, requires = "mnemonic", requires = "announce_address")]
|
||||
pub(crate) enable_coconut: Option<bool>,
|
||||
}
|
||||
|
||||
|
||||
+783
File diff suppressed because one or more lines are too long
@@ -12,12 +12,22 @@ describe("Get circulating supply", (): void => {
|
||||
it("Get circulating supply amounts", async (): Promise<void> => {
|
||||
const response = await contract.getCirculatingSupply();
|
||||
|
||||
let initial: number = +response.initial_supply.amount;
|
||||
let mixmining: number = +response.mixmining_reserve.amount;
|
||||
let vest: number = +response.vesting_tokens.amount;
|
||||
let circsupply: number = +response.circulating_supply.amount;
|
||||
const initial: number = +response.total_supply.amount;
|
||||
const mixmining: number = +response.mixmining_reserve.amount;
|
||||
const vest: number = +response.vesting_tokens.amount;
|
||||
const circsupply: number = +response.circulating_supply.amount;
|
||||
|
||||
expect(typeof response.vesting_tokens.amount).toBe("string");
|
||||
expect(initial - mixmining - vest).toStrictEqual(circsupply);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("Get total supply value", async (): Promise<void> => {
|
||||
const response = await contract.getTotalSupplyValue();
|
||||
expect(typeof response).toBe("number");
|
||||
});
|
||||
|
||||
it("Get circulating supply value", async (): Promise<void> => {
|
||||
const response = await contract.getCirculatingSupplyValue();
|
||||
expect(typeof response).toBe("number");
|
||||
});
|
||||
});
|
||||
|
||||
+127
-57
@@ -17,8 +17,12 @@ describe("Get mixnode data", (): void => {
|
||||
//bond information overview
|
||||
expect(typeof mixnode.bond_information.mix_id).toBe("number");
|
||||
expect(typeof mixnode.bond_information.owner).toBe("string");
|
||||
expect(typeof mixnode.bond_information.original_pledge.amount).toBe("string");
|
||||
expect(typeof mixnode.bond_information.original_pledge.denom).toBe("string");
|
||||
expect(typeof mixnode.bond_information.original_pledge.amount).toBe(
|
||||
"string"
|
||||
);
|
||||
expect(typeof mixnode.bond_information.original_pledge.denom).toBe(
|
||||
"string"
|
||||
);
|
||||
expect(typeof mixnode.bond_information.layer).toBe("number");
|
||||
expect(typeof mixnode.bond_information.bonding_height).toBe("number");
|
||||
expect(typeof mixnode.bond_information.is_unbonding).toBe("boolean");
|
||||
@@ -31,8 +35,12 @@ describe("Get mixnode data", (): void => {
|
||||
|
||||
//mixnode
|
||||
expect(typeof mixnode.bond_information.mix_node.host).toBe("string");
|
||||
expect(mixnode.bond_information.mix_node.http_api_port).toStrictEqual(8000);
|
||||
expect(typeof mixnode.bond_information.mix_node.verloc_port).toBe("number");
|
||||
expect(mixnode.bond_information.mix_node.http_api_port).toStrictEqual(
|
||||
8000
|
||||
);
|
||||
expect(typeof mixnode.bond_information.mix_node.verloc_port).toBe(
|
||||
"number"
|
||||
);
|
||||
expect(typeof mixnode.bond_information.mix_node.mix_port).toBe("number");
|
||||
expect(mixnode.bond_information.mix_node.mix_port).toStrictEqual(1789);
|
||||
expect(mixnode.bond_information.mix_node.verloc_port).toStrictEqual(1790);
|
||||
@@ -40,30 +48,39 @@ describe("Get mixnode data", (): void => {
|
||||
const identitykey = mixnode.bond_information.mix_node.identity_key;
|
||||
if (typeof identitykey === "string") {
|
||||
if (identitykey.length === 43) {
|
||||
return true
|
||||
}
|
||||
else expect(identitykey).toHaveLength(44);
|
||||
return true;
|
||||
} else expect(identitykey).toHaveLength(44);
|
||||
}
|
||||
|
||||
const sphinx = mixnode.bond_information.mix_node.sphinx_key
|
||||
const sphinx = mixnode.bond_information.mix_node.sphinx_key;
|
||||
if (typeof sphinx === "string") {
|
||||
if (sphinx.length === 43) {
|
||||
return true
|
||||
}
|
||||
else expect(sphinx).toHaveLength(44);
|
||||
return true;
|
||||
} else expect(sphinx).toHaveLength(44);
|
||||
}
|
||||
|
||||
//rewarding details
|
||||
expect(typeof mixnode.rewarding_details.cost_params.profit_margin_percent).toBe("string");
|
||||
expect(typeof mixnode.rewarding_details.cost_params.interval_operating_cost.denom).toBe("string");
|
||||
expect(typeof mixnode.rewarding_details.cost_params.interval_operating_cost.amount).toBe("string");
|
||||
expect(
|
||||
typeof mixnode.rewarding_details.cost_params.profit_margin_percent
|
||||
).toBe("string");
|
||||
expect(
|
||||
typeof mixnode.rewarding_details.cost_params.interval_operating_cost
|
||||
.denom
|
||||
).toBe("string");
|
||||
expect(
|
||||
typeof mixnode.rewarding_details.cost_params.interval_operating_cost
|
||||
.amount
|
||||
).toBe("string");
|
||||
expect(typeof mixnode.rewarding_details.operator).toBe("string");
|
||||
expect(typeof mixnode.rewarding_details.delegates).toBe("string");
|
||||
expect(typeof mixnode.rewarding_details.total_unit_reward).toBe("string");
|
||||
expect(typeof mixnode.rewarding_details.unit_delegation).toBe("string");
|
||||
expect(typeof mixnode.rewarding_details.last_rewarded_epoch).toBe("number");
|
||||
expect(typeof mixnode.rewarding_details.unique_delegations).toBe("number");
|
||||
|
||||
expect(typeof mixnode.rewarding_details.last_rewarded_epoch).toBe(
|
||||
"number"
|
||||
);
|
||||
expect(typeof mixnode.rewarding_details.unique_delegations).toBe(
|
||||
"number"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,85 +96,139 @@ describe("Get mixnode data", (): void => {
|
||||
expect(typeof mixnode.family).toBe("string");
|
||||
|
||||
//mixnode details bond info
|
||||
expect(typeof mixnode.mixnode_details.bond_information.mix_id).toBe("number")
|
||||
expect(typeof mixnode.mixnode_details.bond_information.owner).toBe("string");
|
||||
expect(typeof mixnode.mixnode_details.bond_information.original_pledge.amount).toBe("string");
|
||||
expect(typeof mixnode.mixnode_details.bond_information.original_pledge.denom).toBe("string");
|
||||
expect(typeof mixnode.mixnode_details.bond_information.layer).toBe("number");
|
||||
expect(typeof mixnode.mixnode_details.bond_information.bonding_height).toBe("number");
|
||||
expect(typeof mixnode.mixnode_details.bond_information.is_unbonding).toBe("boolean");
|
||||
expect(typeof mixnode.mixnode_details.bond_information.mix_id).toBe(
|
||||
"number"
|
||||
);
|
||||
expect(typeof mixnode.mixnode_details.bond_information.owner).toBe(
|
||||
"string"
|
||||
);
|
||||
expect(
|
||||
typeof mixnode.mixnode_details.bond_information.original_pledge.amount
|
||||
).toBe("string");
|
||||
expect(
|
||||
typeof mixnode.mixnode_details.bond_information.original_pledge.denom
|
||||
).toBe("string");
|
||||
expect(typeof mixnode.mixnode_details.bond_information.layer).toBe(
|
||||
"number"
|
||||
);
|
||||
expect(
|
||||
typeof mixnode.mixnode_details.bond_information.bonding_height
|
||||
).toBe("number");
|
||||
expect(typeof mixnode.mixnode_details.bond_information.is_unbonding).toBe(
|
||||
"boolean"
|
||||
);
|
||||
|
||||
if (mixnode.mixnode_details.bond_information.proxy === null) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
expect(typeof mixnode.mixnode_details.bond_information.proxy).toBe("string");
|
||||
} else {
|
||||
expect(typeof mixnode.mixnode_details.bond_information.proxy).toBe(
|
||||
"string"
|
||||
);
|
||||
}
|
||||
|
||||
//mixnode
|
||||
expect(typeof mixnode.mixnode_details.bond_information.mix_node.host).toBe("string")
|
||||
expect(mixnode.mixnode_details.bond_information.mix_node.http_api_port).toStrictEqual(8000);
|
||||
expect(typeof mixnode.mixnode_details.bond_information.mix_node.verloc_port).toBe("number")
|
||||
expect(typeof mixnode.mixnode_details.bond_information.mix_node.mix_port).toBe("number")
|
||||
expect(mixnode.mixnode_details.bond_information.mix_node.mix_port).toStrictEqual(1789);
|
||||
expect(mixnode.mixnode_details.bond_information.mix_node.verloc_port).toStrictEqual(1790)
|
||||
expect(
|
||||
typeof mixnode.mixnode_details.bond_information.mix_node.host
|
||||
).toBe("string");
|
||||
expect(
|
||||
mixnode.mixnode_details.bond_information.mix_node.http_api_port
|
||||
).toStrictEqual(8000);
|
||||
expect(
|
||||
typeof mixnode.mixnode_details.bond_information.mix_node.verloc_port
|
||||
).toBe("number");
|
||||
expect(
|
||||
typeof mixnode.mixnode_details.bond_information.mix_node.mix_port
|
||||
).toBe("number");
|
||||
expect(
|
||||
mixnode.mixnode_details.bond_information.mix_node.mix_port
|
||||
).toStrictEqual(1789);
|
||||
expect(
|
||||
mixnode.mixnode_details.bond_information.mix_node.verloc_port
|
||||
).toStrictEqual(1790);
|
||||
|
||||
let identitykey2 = mixnode.mixnode_details.bond_information.mix_node.identity_key
|
||||
const identitykey2 =
|
||||
mixnode.mixnode_details.bond_information.mix_node.identity_key;
|
||||
if (typeof identitykey2 === "string") {
|
||||
if (identitykey2.length === 43) {
|
||||
return true
|
||||
}
|
||||
else expect(identitykey2).toHaveLength(44);
|
||||
return true;
|
||||
} else expect(identitykey2).toHaveLength(44);
|
||||
}
|
||||
|
||||
let sphinx2 = mixnode.mixnode_details.bond_information.mix_node.sphinx_key
|
||||
const sphinx2 =
|
||||
mixnode.mixnode_details.bond_information.mix_node.sphinx_key;
|
||||
if (typeof sphinx2 === "string") {
|
||||
if (sphinx2.length === 43) {
|
||||
return true
|
||||
}
|
||||
else expect(sphinx2).toHaveLength(44);
|
||||
return true;
|
||||
} else expect(sphinx2).toHaveLength(44);
|
||||
}
|
||||
|
||||
//mixnode rewarding info
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.cost_params.profit_margin_percent).toBe("string")
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.cost_params.interval_operating_cost.denom).toBe("string")
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.cost_params.interval_operating_cost.amount).toBe("string")
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.operator).toBe("string")
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.delegates).toBe("string")
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.total_unit_reward).toBe("string")
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.unit_delegation).toBe("string")
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.last_rewarded_epoch).toBe("number")
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.unique_delegations).toBe("number")
|
||||
|
||||
expect(
|
||||
typeof mixnode.mixnode_details.rewarding_details.cost_params
|
||||
.profit_margin_percent
|
||||
).toBe("string");
|
||||
expect(
|
||||
typeof mixnode.mixnode_details.rewarding_details.cost_params
|
||||
.interval_operating_cost.denom
|
||||
).toBe("string");
|
||||
expect(
|
||||
typeof mixnode.mixnode_details.rewarding_details.cost_params
|
||||
.interval_operating_cost.amount
|
||||
).toBe("string");
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.operator).toBe(
|
||||
"string"
|
||||
);
|
||||
expect(typeof mixnode.mixnode_details.rewarding_details.delegates).toBe(
|
||||
"string"
|
||||
);
|
||||
expect(
|
||||
typeof mixnode.mixnode_details.rewarding_details.total_unit_reward
|
||||
).toBe("string");
|
||||
expect(
|
||||
typeof mixnode.mixnode_details.rewarding_details.unit_delegation
|
||||
).toBe("string");
|
||||
expect(
|
||||
typeof mixnode.mixnode_details.rewarding_details.last_rewarded_epoch
|
||||
).toBe("number");
|
||||
expect(
|
||||
typeof mixnode.mixnode_details.rewarding_details.unique_delegations
|
||||
).toBe("number");
|
||||
});
|
||||
});
|
||||
|
||||
it("Get active mixnodes", async (): Promise<void> => {
|
||||
const response = await contract.getActiveMixnodes();
|
||||
response.forEach(function (mixnode) {
|
||||
expect(mixnode.rewarding_details.cost_params.profit_margin_percent).toBeTruthy()
|
||||
expect(typeof mixnode.bond_information.layer).toBe("number")
|
||||
expect(
|
||||
mixnode.rewarding_details.cost_params.profit_margin_percent
|
||||
).toBeTruthy();
|
||||
expect(typeof mixnode.bond_information.layer).toBe("number");
|
||||
});
|
||||
});
|
||||
|
||||
it("Get active mixnodes detailed", async (): Promise<void> => {
|
||||
const response = await contract.getActiveMixnodesDetailed();
|
||||
response.forEach(function (mixnode) {
|
||||
expect(mixnode.mixnode_details.rewarding_details.cost_params.profit_margin_percent).toBeTruthy()
|
||||
expect(
|
||||
mixnode.mixnode_details.rewarding_details.cost_params
|
||||
.profit_margin_percent
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("Get rewarded mixnodes", async (): Promise<void> => {
|
||||
const response = await contract.getRewardedMixnodes();
|
||||
response.forEach(function (mixnode) {
|
||||
expect(mixnode.rewarding_details.last_rewarded_epoch).toBeTruthy()
|
||||
expect(mixnode.rewarding_details.last_rewarded_epoch).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("Get rewarded mixnodes detailed", async (): Promise<void> => {
|
||||
const response = await contract.getRewardedMixnodesDetailed();
|
||||
response.forEach(function (mixnode) {
|
||||
expect(mixnode.mixnode_details.rewarding_details.last_rewarded_epoch).toBeTruthy()
|
||||
expect(
|
||||
mixnode.mixnode_details.rewarding_details.last_rewarded_epoch
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -167,5 +238,4 @@ describe("Get mixnode data", (): void => {
|
||||
expect(typeof value).toBe("number");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -46,5 +46,4 @@ describe("Get gateway data", (): void => {
|
||||
expect(typeof response.last_hour).toBe("number");
|
||||
expect(typeof response.last_day).toBe("number");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -40,9 +40,9 @@ describe("Get mixnode data", (): void => {
|
||||
const response = await status.getMixnodeHistory(identity_key);
|
||||
|
||||
response.history.forEach((x) => {
|
||||
console.log(x.date);
|
||||
console.log(x.uptime);
|
||||
})
|
||||
expect(typeof x.date).toBe("string");
|
||||
expect(typeof x.uptime).toBe("number");
|
||||
});
|
||||
|
||||
expect(identity_key).toStrictEqual(response.mix_id);
|
||||
expect(typeof response.owner).toBe("string");
|
||||
@@ -67,7 +67,9 @@ describe("Get mixnode data", (): void => {
|
||||
const identity_key = config.environmnetConfig.mix_id;
|
||||
const response = await status.getMixnodeRewardComputation(identity_key);
|
||||
|
||||
expect(response.reward_params.interval.sybil_resistance).toStrictEqual("0.3");
|
||||
expect(response.reward_params.interval.sybil_resistance).toStrictEqual(
|
||||
"0.3"
|
||||
);
|
||||
expect(response.reward_params.active_set_size).toStrictEqual(240);
|
||||
expect(typeof response.reward_params.interval.reward_pool).toBe("string");
|
||||
});
|
||||
@@ -79,42 +81,54 @@ describe("Get mixnode data", (): void => {
|
||||
expect(typeof response.in_active).toBe("string");
|
||||
});
|
||||
|
||||
it("Get all mixnodes inclusion probability", async (): Promise<void> => {
|
||||
it("Get all mixnodes inclusion probabilities", async (): Promise<void> => {
|
||||
const response = await status.getAllMixnodeInclusionProbability();
|
||||
|
||||
expect(response.inclusion_probabilities).toBeTruthy();
|
||||
const array = response.inclusion_probabilities;
|
||||
array.forEach((x) => {
|
||||
expect(typeof x.in_reserve).toBe("number");
|
||||
expect(typeof x.mix_id).toBe("number");
|
||||
});
|
||||
expect(typeof response.elapsed.nanos).toBe("number");
|
||||
});
|
||||
|
||||
it("Get all mixnodes", async (): Promise<void> => {
|
||||
const response = await status.getDetailedMixnodes();
|
||||
|
||||
expect(typeof response.stake_saturation).toBe("string");
|
||||
response.forEach((x) => {
|
||||
expect(typeof x.mixnode_details.bond_information.mix_id).toBe("number");
|
||||
expect(typeof x.mixnode_details.bond_information.layer).toBe("number");
|
||||
expect(typeof x.stake_saturation).toBe("string");
|
||||
});
|
||||
});
|
||||
|
||||
it("Get all rewarded mixnodes", async (): Promise<void> => {
|
||||
const response = await status.getDetailedRewardedMixnodes();
|
||||
|
||||
expect(typeof response.mixnode_details.rewarding_details.last_rewarded_epoch).toBe("number");
|
||||
response.forEach((x) => {
|
||||
expect(typeof x.mixnode_details.bond_information.mix_id).toBe("number");
|
||||
expect(typeof x.mixnode_details.bond_information.layer).toBe("number");
|
||||
expect(typeof x.stake_saturation).toBe("string");
|
||||
});
|
||||
});
|
||||
|
||||
it("Get all active mixnodes", async (): Promise<void> => {
|
||||
const response = await status.getDetailedActiveMixnodes();
|
||||
|
||||
expect(typeof response.mixnode_details.bond_information.layer).toBe("number");
|
||||
response.forEach((x) => {
|
||||
expect(typeof x.mixnode_details.bond_information.mix_id).toBe("number");
|
||||
expect(typeof x.mixnode_details.bond_information.layer).toBe("number");
|
||||
expect(typeof x.stake_saturation).toBe("string");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("Compute mixnode reward estimation", (): void => {
|
||||
beforeAll(async (): Promise<void> => {
|
||||
status = new Status();
|
||||
config = ConfigHandler.getInstance();
|
||||
});
|
||||
it("with correct data", async (): Promise<void> => {
|
||||
const response = await status.sendMixnodeRewardEstimatedComputation(8);
|
||||
const body =
|
||||
|
||||
expect(typeof response.estimation.total_node_reward).toBe("string");
|
||||
});
|
||||
|
||||
// TODO Fix this test
|
||||
it.skip("with correct data", async (): Promise<void> => {
|
||||
const response = await status.sendMixnodeRewardEstimatedComputation(8);
|
||||
const body = expect(typeof response.estimation.total_node_reward).toBe(
|
||||
"string"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,4 +12,18 @@ export default class ContractCache extends APIClient {
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getTotalSupplyValue(): Promise<number> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `circulating-supply/total-supply-value`,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getCirculatingSupplyValue(): Promise<number> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `circulating-supply/circulating-supply-value`,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
AllGateways,
|
||||
AllMixnodes,
|
||||
EpochRewardParams,
|
||||
BlacklistedGateways,
|
||||
BlacklistedMixnodes,
|
||||
CurrentEpoch,
|
||||
} from "../types/ContractCacheTypes";
|
||||
import { APIClient } from "./abstracts/APIClient";
|
||||
@@ -62,14 +64,14 @@ export default class ContractCache extends APIClient {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getBlacklistedMixnodes(): Promise<[]> {
|
||||
public async getBlacklistedMixnodes(): Promise<BlacklistedMixnodes[]> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `mixnodes/blacklisted`,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getBlacklistedGateways(): Promise<[]> {
|
||||
public async getBlacklistedGateways(): Promise<BlacklistedGateways[]> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `gateways/blacklisted`,
|
||||
});
|
||||
|
||||
@@ -147,13 +147,13 @@ export default class Status extends APIClient {
|
||||
|
||||
public async getAllMixnodeInclusionProbability(): Promise<InclusionProbabilities> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `/mixnodes/inclusion-probability`,
|
||||
route: `/mixnodes/inclusion_probability`,
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getDetailedMixnodes(): Promise<DetailedMixnodes> {
|
||||
public async getDetailedMixnodes(): Promise<DetailedMixnodes[]> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `/mixnodes/detailed`,
|
||||
});
|
||||
@@ -161,7 +161,7 @@ export default class Status extends APIClient {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getDetailedRewardedMixnodes(): Promise<DetailedMixnodes> {
|
||||
public async getDetailedRewardedMixnodes(): Promise<DetailedMixnodes[]> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `/mixnodes/rewarded/detailed`,
|
||||
});
|
||||
@@ -169,7 +169,7 @@ export default class Status extends APIClient {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getDetailedActiveMixnodes(): Promise<DetailedMixnodes> {
|
||||
public async getDetailedActiveMixnodes(): Promise<DetailedMixnodes[]> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `/mixnodes/active/detailed`,
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
export type Detailed = {
|
||||
initial_supply: InitialSupply;
|
||||
total_supply: TotalSupply;
|
||||
mixmining_reserve: MixminingReserve;
|
||||
vesting_tokens: VestingTokens;
|
||||
circulating_supply: CirculatingSupply;
|
||||
};
|
||||
|
||||
export type InitialSupply = {
|
||||
export type TotalSupply = {
|
||||
demon: "unym";
|
||||
amount: string;
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export interface AllMixnodes {
|
||||
export type AllMixnodes = {
|
||||
bond_information: BondInformation;
|
||||
rewarding_details: RewardingDetails;
|
||||
}
|
||||
};
|
||||
|
||||
export interface BondInformation {
|
||||
export type BondInformation = {
|
||||
mix_id: number;
|
||||
owner: string;
|
||||
original_pledge: OriginalPledge;
|
||||
@@ -12,9 +12,9 @@ export interface BondInformation {
|
||||
proxy: string;
|
||||
bonding_height: number;
|
||||
is_unbonding: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
export interface RewardingDetails {
|
||||
export type RewardingDetails = {
|
||||
cost_params: CostParams;
|
||||
operator: string;
|
||||
delegates: string;
|
||||
@@ -22,29 +22,29 @@ export interface RewardingDetails {
|
||||
unit_delegation: string;
|
||||
last_rewarded_epoch: number;
|
||||
unique_delegations: number;
|
||||
}
|
||||
};
|
||||
|
||||
export interface CostParams {
|
||||
export type CostParams = {
|
||||
profit_margin_percent: string;
|
||||
interval_operating_cost: IntervalOperatingCost;
|
||||
}
|
||||
};
|
||||
|
||||
export interface IntervalOperatingCost {
|
||||
export type IntervalOperatingCost = {
|
||||
denom: string;
|
||||
amount: string;
|
||||
}
|
||||
};
|
||||
|
||||
export interface OriginalPledge {
|
||||
export type OriginalPledge = {
|
||||
denom: string;
|
||||
amount: string;
|
||||
}
|
||||
};
|
||||
|
||||
export interface TotalDelegation {
|
||||
export type TotalDelegation = {
|
||||
denom: string;
|
||||
amount: string;
|
||||
}
|
||||
};
|
||||
|
||||
export interface Mixnode {
|
||||
export type Mixnode = {
|
||||
host: string;
|
||||
mix_port: number;
|
||||
verloc_port: number;
|
||||
@@ -52,9 +52,9 @@ export interface Mixnode {
|
||||
sphinx_key: string;
|
||||
identity_key: string;
|
||||
version: string;
|
||||
}
|
||||
};
|
||||
|
||||
export interface MixnodeBond {
|
||||
export type MixnodeBond = {
|
||||
pledge_amount: OriginalPledge;
|
||||
total_delegation: TotalDelegation;
|
||||
owner: string;
|
||||
@@ -63,9 +63,9 @@ export interface MixnodeBond {
|
||||
mix_node: Mixnode;
|
||||
proxy: string;
|
||||
accumulated_rewards: string;
|
||||
}
|
||||
};
|
||||
|
||||
export interface MixnodesDetailed {
|
||||
export type MixnodesDetailed = {
|
||||
mixnode_details: AllMixnodes;
|
||||
stake_saturation: string;
|
||||
uncapped_stake_saturation: string;
|
||||
@@ -73,7 +73,11 @@ export interface MixnodesDetailed {
|
||||
estimated_operator_apy: string;
|
||||
estimated_delegators_apy: string;
|
||||
family: string;
|
||||
}
|
||||
};
|
||||
|
||||
export type BlacklistedMixnodes = {};
|
||||
|
||||
export type BlacklistedGateways = {};
|
||||
|
||||
export interface Gateway {
|
||||
host: string;
|
||||
@@ -93,13 +97,13 @@ export interface AllGateways {
|
||||
proxy: string;
|
||||
}
|
||||
|
||||
export interface EpochRewardParams {
|
||||
export type EpochRewardParams = {
|
||||
interval: Interval;
|
||||
rewarded_set_size: number;
|
||||
active_set_size: number;
|
||||
}
|
||||
};
|
||||
|
||||
export interface Interval {
|
||||
export type Interval = {
|
||||
reward_pool: string;
|
||||
staking_supply: string;
|
||||
staking_supply_scale_factor: string;
|
||||
@@ -108,18 +112,18 @@ export interface Interval {
|
||||
sybil_resistance: string;
|
||||
active_set_work_factor: string;
|
||||
interval_pool_emission: string;
|
||||
}
|
||||
};
|
||||
|
||||
export interface CurrentEpoch {
|
||||
export type CurrentEpoch = {
|
||||
id: number;
|
||||
epochs_in_interval: number;
|
||||
current_epoch_start: string;
|
||||
current_epoch_id: number;
|
||||
epoch_length: EpochLength;
|
||||
total_elapsed_epochs: number;
|
||||
}
|
||||
};
|
||||
|
||||
export interface EpochLength {
|
||||
export type EpochLength = {
|
||||
secs: number;
|
||||
nanos: number;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
"typeRoots": ["node_modules/@types"],
|
||||
"alwaysStrict": true
|
||||
},
|
||||
"include": ["src/**/*", "tests/functional_test/*/*"],
|
||||
"include": ["src/**/*", "functional_test/*/*"],
|
||||
"exclude": ["unit_test/**/*"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "nym-api-requests"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bs58 = "0.4.0"
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
cosmwasm-std = { version = "1.0.0", default-features = false }
|
||||
getset = "0.1.1"
|
||||
schemars = { version = "0.8", features = ["preserve_order"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
ts-rs = { version = "6.1.2", optional = true }
|
||||
|
||||
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||
mixnet-contract-common = { path= "../../common/cosmwasm-smart-contracts/mixnet-contract" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
coconut = ["coconut-interface"]
|
||||
generate-ts = ["ts-rs"]
|
||||
@@ -0,0 +1,158 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmrs::AccountId;
|
||||
use getset::{CopyGetters, Getters};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use coconut_interface::{
|
||||
error::CoconutInterfaceError, Attribute, Base58, BlindSignRequest, Credential, VerificationKey,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, Getters, CopyGetters)]
|
||||
pub struct VerifyCredentialBody {
|
||||
#[getset(get = "pub")]
|
||||
credential: Credential,
|
||||
#[getset(get = "pub")]
|
||||
proposal_id: u64,
|
||||
#[getset(get = "pub")]
|
||||
gateway_cosmos_addr: AccountId,
|
||||
}
|
||||
|
||||
impl VerifyCredentialBody {
|
||||
pub fn new(
|
||||
credential: Credential,
|
||||
proposal_id: u64,
|
||||
gateway_cosmos_addr: AccountId,
|
||||
) -> VerifyCredentialBody {
|
||||
VerifyCredentialBody {
|
||||
credential,
|
||||
proposal_id,
|
||||
gateway_cosmos_addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct VerifyCredentialResponse {
|
||||
pub verification_result: bool,
|
||||
}
|
||||
|
||||
impl VerifyCredentialResponse {
|
||||
pub fn new(verification_result: bool) -> Self {
|
||||
VerifyCredentialResponse {
|
||||
verification_result,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All strings are base58 encoded representations of structs
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, Getters, CopyGetters)]
|
||||
pub struct BlindSignRequestBody {
|
||||
#[getset(get = "pub")]
|
||||
blind_sign_request: BlindSignRequest,
|
||||
#[getset(get = "pub")]
|
||||
tx_hash: String,
|
||||
#[getset(get = "pub")]
|
||||
signature: String,
|
||||
public_attributes: Vec<String>,
|
||||
#[getset(get = "pub")]
|
||||
public_attributes_plain: Vec<String>,
|
||||
#[getset(get = "pub")]
|
||||
total_params: u32,
|
||||
}
|
||||
|
||||
impl BlindSignRequestBody {
|
||||
pub fn new(
|
||||
blind_sign_request: &BlindSignRequest,
|
||||
tx_hash: String,
|
||||
signature: String,
|
||||
public_attributes: &[Attribute],
|
||||
public_attributes_plain: Vec<String>,
|
||||
total_params: u32,
|
||||
) -> BlindSignRequestBody {
|
||||
BlindSignRequestBody {
|
||||
blind_sign_request: blind_sign_request.clone(),
|
||||
tx_hash,
|
||||
signature,
|
||||
public_attributes: public_attributes
|
||||
.iter()
|
||||
.map(|attr| attr.to_bs58())
|
||||
.collect(),
|
||||
public_attributes_plain,
|
||||
total_params,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn public_attributes(&self) -> Vec<Attribute> {
|
||||
self.public_attributes
|
||||
.iter()
|
||||
.map(|x| Attribute::try_from_bs58(x).unwrap())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct BlindedSignatureResponse {
|
||||
pub remote_key: [u8; 32],
|
||||
pub encrypted_signature: Vec<u8>,
|
||||
}
|
||||
|
||||
impl BlindedSignatureResponse {
|
||||
pub fn new(encrypted_signature: Vec<u8>, remote_key: [u8; 32]) -> BlindedSignatureResponse {
|
||||
BlindedSignatureResponse {
|
||||
encrypted_signature,
|
||||
remote_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_base58_string(&self) -> String {
|
||||
bs58::encode(&self.to_bytes()).into_string()
|
||||
}
|
||||
|
||||
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, CoconutInterfaceError> {
|
||||
let bytes = bs58::decode(val).into_vec()?;
|
||||
Self::from_bytes(&bytes)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = self.remote_key.to_vec();
|
||||
bytes.extend_from_slice(&self.encrypted_signature);
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, CoconutInterfaceError> {
|
||||
if bytes.len() < 32 {
|
||||
return Err(CoconutInterfaceError::InvalidByteLength(bytes.len(), 32));
|
||||
}
|
||||
let mut remote_key = [0u8; 32];
|
||||
remote_key.copy_from_slice(&bytes[..32]);
|
||||
let encrypted_signature = bytes[32..].to_vec();
|
||||
Ok(BlindedSignatureResponse {
|
||||
remote_key,
|
||||
encrypted_signature,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct VerificationKeyResponse {
|
||||
pub key: VerificationKey,
|
||||
}
|
||||
|
||||
impl VerificationKeyResponse {
|
||||
pub fn new(key: VerificationKey) -> VerificationKeyResponse {
|
||||
VerificationKeyResponse { key }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CosmosAddressResponse {
|
||||
pub addr: AccountId,
|
||||
}
|
||||
|
||||
impl CosmosAddressResponse {
|
||||
pub fn new(addr: AccountId) -> CosmosAddressResponse {
|
||||
CosmosAddressResponse { addr }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
pub mod coconut;
|
||||
pub mod models;
|
||||
|
||||
pub trait Deprecatable {
|
||||
fn deprecate(self) -> Deprecated<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deprecatable for T {}
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
pub struct Deprecated<T> {
|
||||
pub deprecated: bool,
|
||||
#[serde(flatten)]
|
||||
pub response: T,
|
||||
}
|
||||
|
||||
impl<T> From<T> for Deprecated<T> {
|
||||
fn from(response: T) -> Self {
|
||||
Deprecated {
|
||||
deprecated: true,
|
||||
response,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{Coin, Decimal};
|
||||
use mixnet_contract_common::families::FamilyHead;
|
||||
use mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use mixnet_contract_common::reward_params::{Performance, RewardingParams};
|
||||
use mixnet_contract_common::rewarding::RewardEstimate;
|
||||
use mixnet_contract_common::{
|
||||
IdentityKey, Interval, MixId, MixNode, Percent, RewardedSetNodeStatus,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt, time::Duration};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
pub struct RequestError {
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl RequestError {
|
||||
pub fn new<S: Into<String>>(msg: S) -> Self {
|
||||
RequestError {
|
||||
message: msg.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message(&self) -> &str {
|
||||
&self.message
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/MixnodeStatus.ts")
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MixnodeStatus {
|
||||
Active, // in both the active set and the rewarded set
|
||||
Standby, // only in the rewarded set
|
||||
Inactive, // in neither the rewarded set nor the active set, but is bonded
|
||||
NotFound, // doesn't even exist in the bonded set
|
||||
}
|
||||
|
||||
impl From<MixnodeStatus> for Option<RewardedSetNodeStatus> {
|
||||
fn from(status: MixnodeStatus) -> Self {
|
||||
match status {
|
||||
MixnodeStatus::Active => Some(RewardedSetNodeStatus::Active),
|
||||
MixnodeStatus::Standby => Some(RewardedSetNodeStatus::Standby),
|
||||
MixnodeStatus::Inactive => None,
|
||||
MixnodeStatus::NotFound => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MixnodeStatus {
|
||||
pub fn is_active(&self) -> bool {
|
||||
*self == MixnodeStatus::Active
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/MixnodeCoreStatusResponse.ts")
|
||||
)]
|
||||
pub struct MixnodeCoreStatusResponse {
|
||||
pub mix_id: MixId,
|
||||
pub count: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/GatewayCoreStatusResponse.ts")
|
||||
)]
|
||||
pub struct GatewayCoreStatusResponse {
|
||||
pub identity: String,
|
||||
pub count: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/MixnodeStatusResponse.ts")
|
||||
)]
|
||||
pub struct MixnodeStatusResponse {
|
||||
pub status: MixnodeStatus,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct MixNodeBondAnnotated {
|
||||
pub mixnode_details: MixNodeDetails,
|
||||
pub stake_saturation: StakeSaturation,
|
||||
pub uncapped_stake_saturation: StakeSaturation,
|
||||
pub performance: Performance,
|
||||
pub estimated_operator_apy: Decimal,
|
||||
pub estimated_delegators_apy: Decimal,
|
||||
pub family: Option<FamilyHead>,
|
||||
}
|
||||
|
||||
impl MixNodeBondAnnotated {
|
||||
pub fn mix_node(&self) -> &MixNode {
|
||||
&self.mixnode_details.bond_information.mix_node
|
||||
}
|
||||
|
||||
pub fn mix_id(&self) -> MixId {
|
||||
self.mixnode_details.mix_id()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ComputeRewardEstParam {
|
||||
pub performance: Option<Performance>,
|
||||
pub active_in_rewarded_set: Option<bool>,
|
||||
pub pledge_amount: Option<u64>,
|
||||
pub total_delegation: Option<u64>,
|
||||
pub interval_operating_cost: Option<Coin>,
|
||||
pub profit_margin_percent: Option<Percent>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/RewardEstimationResponse.ts")
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct RewardEstimationResponse {
|
||||
pub estimation: RewardEstimate,
|
||||
pub reward_params: RewardingParams,
|
||||
pub epoch: Interval,
|
||||
#[cfg_attr(feature = "generate-ts", ts(type = "number"))]
|
||||
pub as_at: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct UptimeResponse {
|
||||
pub mix_id: MixId,
|
||||
pub avg_uptime: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/StakeSaturationResponse.ts")
|
||||
)]
|
||||
pub struct StakeSaturationResponse {
|
||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||
pub saturation: StakeSaturation,
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
|
||||
pub uncapped_saturation: StakeSaturation,
|
||||
pub as_at: i64,
|
||||
}
|
||||
|
||||
pub type StakeSaturation = Decimal;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/SelectionChance.ts")
|
||||
)]
|
||||
pub enum SelectionChance {
|
||||
High,
|
||||
Good,
|
||||
Low,
|
||||
}
|
||||
|
||||
impl From<f64> for SelectionChance {
|
||||
fn from(p: f64) -> SelectionChance {
|
||||
match p {
|
||||
p if p >= 0.7 => SelectionChance::High,
|
||||
p if p >= 0.3 => SelectionChance::Good,
|
||||
_ => SelectionChance::Low,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Decimal> for SelectionChance {
|
||||
fn from(p: Decimal) -> Self {
|
||||
match p {
|
||||
p if p >= Decimal::from_ratio(70u32, 100u32) => SelectionChance::High,
|
||||
p if p >= Decimal::from_ratio(30u32, 100u32) => SelectionChance::Good,
|
||||
_ => SelectionChance::Low,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SelectionChance {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
SelectionChance::High => write!(f, "High"),
|
||||
SelectionChance::Good => write!(f, "Good"),
|
||||
SelectionChance::Low => write!(f, "Low"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/InclusionProbabilityResponse.ts")
|
||||
)]
|
||||
pub struct InclusionProbabilityResponse {
|
||||
pub in_active: SelectionChance,
|
||||
pub in_reserve: SelectionChance,
|
||||
}
|
||||
|
||||
impl fmt::Display for InclusionProbabilityResponse {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"in_active: {}, in_reserve: {}",
|
||||
self.in_active, self.in_reserve
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, schemars::JsonSchema)]
|
||||
pub struct AllInclusionProbabilitiesResponse {
|
||||
pub inclusion_probabilities: Vec<InclusionProbability>,
|
||||
pub samples: u64,
|
||||
pub elapsed: Duration,
|
||||
pub delta_max: f64,
|
||||
pub delta_l2: f64,
|
||||
pub as_at: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, schemars::JsonSchema)]
|
||||
pub struct InclusionProbability {
|
||||
pub mix_id: MixId,
|
||||
pub in_active: f64,
|
||||
pub in_reserve: f64,
|
||||
}
|
||||
|
||||
type Uptime = u8;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct MixnodeStatusReportResponse {
|
||||
pub mix_id: MixId,
|
||||
pub identity: IdentityKey,
|
||||
pub owner: String,
|
||||
pub most_recent: Uptime,
|
||||
pub last_hour: Uptime,
|
||||
pub last_day: Uptime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct GatewayStatusReportResponse {
|
||||
pub identity: String,
|
||||
pub owner: String,
|
||||
pub most_recent: Uptime,
|
||||
pub last_hour: Uptime,
|
||||
pub last_day: Uptime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct HistoricalUptimeResponse {
|
||||
pub date: String,
|
||||
pub uptime: Uptime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct MixnodeUptimeHistoryResponse {
|
||||
pub mix_id: MixId,
|
||||
pub identity: String,
|
||||
pub owner: String,
|
||||
pub history: Vec<HistoricalUptimeResponse>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct GatewayUptimeHistoryResponse {
|
||||
pub identity: String,
|
||||
pub owner: String,
|
||||
pub history: Vec<HistoricalUptimeResponse>,
|
||||
}
|
||||
@@ -2,20 +2,6 @@
|
||||
|
||||
## UNRELEASED
|
||||
|
||||
## [nym-connect-v1.1.9](https://github.com/nymtech/nym/tree/nym-connect-v1.1.9) (2023-02-14)
|
||||
|
||||
- Button animations ([#2949])
|
||||
- add effect when the button is clicked ([#2947])
|
||||
- UI to select gateways based on some performance criteria by checking gateways' routing score from nym-api ([#2942])
|
||||
- client health check when connecting ([#2859])
|
||||
- allow user to select own gateway ([#2952])
|
||||
|
||||
[#2952]: https://github.com/nymtech/nym/issues/2952
|
||||
[#2949]: https://github.com/nymtech/nym/issues/2949
|
||||
[#2947]: https://github.com/nymtech/nym/issues/2947
|
||||
[#2942]: https://github.com/nymtech/nym/issues/2942
|
||||
[#2859]: https://github.com/nymtech/nym/issues/2859
|
||||
|
||||
## [nym-connect-v1.1.8](https://github.com/nymtech/nym/tree/nym-connect-v1.1.8) (2023-01-31)
|
||||
|
||||
- Add supported apps in the menu + update guide ([#2868])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nym/nym-connect",
|
||||
"version": "1.1.9",
|
||||
"version": "1.1.8",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-connect"
|
||||
version = "1.1.9"
|
||||
version = "1.1.8"
|
||||
description = "nym-connect"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
@@ -45,8 +45,6 @@ url = "2.2"
|
||||
yaml-rust = "0.4"
|
||||
|
||||
client-core = { path = "../../clients/client-core" }
|
||||
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
|
||||
contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common"}
|
||||
config-common = { path = "../../common/config", package = "config" }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
logging = { path = "../../common/logging"}
|
||||
|
||||
@@ -52,7 +52,6 @@ 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,35 +1,21 @@
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::models::{
|
||||
DirectoryService, DirectoryServiceProvider, HarbourMasterService, PagedResult,
|
||||
};
|
||||
use contracts_common::types::Percent;
|
||||
use nym_api_requests::models::GatewayBondAnnotated;
|
||||
use crate::models::{DirectoryService, HarbourMasterService, PagedResult};
|
||||
|
||||
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<DirectoryServiceProvider>> {
|
||||
pub async fn get_services() -> Result<Vec<DirectoryService>> {
|
||||
log::trace!("Fetching services");
|
||||
let services_res = reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL)
|
||||
let res = reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL)
|
||||
.await?
|
||||
.json::<Vec<DirectoryService>>()
|
||||
.await?;
|
||||
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);
|
||||
log::trace!("Received: {:#?}", res);
|
||||
|
||||
// TODO: get paged
|
||||
log::trace!("Fetching active services");
|
||||
@@ -41,7 +27,7 @@ pub async fn get_services() -> Result<Vec<DirectoryServiceProvider>> {
|
||||
|
||||
let mut filtered: Vec<DirectoryService> = vec![];
|
||||
|
||||
for service in &services_res {
|
||||
for service in &res {
|
||||
let items: _ = service
|
||||
.items
|
||||
.clone()
|
||||
@@ -61,32 +47,5 @@ pub async fn get_services() -> Result<Vec<DirectoryServiceProvider>> {
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
Ok(filtered)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-connect",
|
||||
"version": "1.1.9"
|
||||
"version": "1.1.8"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { forage } from '@tauri-apps/tauri-forage';
|
||||
import { useClientContext } from './context/main';
|
||||
import { useTauriEvents } from './utils';
|
||||
import { AppRoutes } from './routes';
|
||||
import { Connected } from './pages/connection/Connected';
|
||||
|
||||
export const App: FCWithChildren = () => {
|
||||
const context = useClientContext();
|
||||
const [busy, setBusy] = React.useState<boolean>();
|
||||
|
||||
useTauriEvents('help://clear-storage', (_event) => {
|
||||
console.log('About to clear local storage...');
|
||||
// clear local storage
|
||||
try {
|
||||
forage.clear()();
|
||||
console.log('Local storage cleared');
|
||||
} catch (e) {
|
||||
console.error('Failed to clear local storage', e);
|
||||
}
|
||||
});
|
||||
|
||||
const handleConnectClick = React.useCallback(async () => {
|
||||
const currentStatus = context.connectionStatus;
|
||||
if (currentStatus === 'connected' || currentStatus === 'disconnected') {
|
||||
setBusy(true);
|
||||
|
||||
// eslint-disable-next-line default-case
|
||||
switch (currentStatus) {
|
||||
case 'disconnected':
|
||||
await context.startConnecting();
|
||||
context.setConnectedSince(DateTime.now());
|
||||
break;
|
||||
case 'connected':
|
||||
await context.startDisconnecting();
|
||||
context.setConnectedSince(undefined);
|
||||
break;
|
||||
}
|
||||
setBusy(false);
|
||||
}
|
||||
}, [context.connectionStatus]);
|
||||
|
||||
if (context.connectionStatus === 'disconnected' || context.connectionStatus === 'connecting') {
|
||||
return <AppRoutes />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Connected
|
||||
status={context.connectionStatus}
|
||||
showInfoModal={context.showInfoModal}
|
||||
closeInfoModal={() => context.setShowInfoModal(false)}
|
||||
busy={busy}
|
||||
onConnectClick={handleConnectClick}
|
||||
ipAddress="127.0.0.1"
|
||||
port={1080}
|
||||
gatewayPerformance={context.gatewayPerformance}
|
||||
connectedSince={context.connectedSince}
|
||||
serviceProvider={context.selectedProvider}
|
||||
stats={[
|
||||
{
|
||||
label: 'in:',
|
||||
totalBytes: 1024,
|
||||
rateBytesPerSecond: 1024 * 1024 * 1024 + 10,
|
||||
},
|
||||
{
|
||||
label: 'out:',
|
||||
totalBytes: 1024 * 1024 * 1024 * 1024 * 20,
|
||||
rateBytesPerSecond: 1024 * 1024 + 10,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,28 +1,10 @@
|
||||
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
|
||||
@@ -32,7 +14,7 @@ export const AppWindowFrame: FCWithChildren = ({ children }) => {
|
||||
height: '100vh',
|
||||
}}
|
||||
>
|
||||
<CustomTitleBar path={location.pathname} onBack={onBack()} />
|
||||
<CustomTitleBar path={location.pathname} />
|
||||
<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,14 +83,17 @@ export const ConnectionStatus: FCWithChildren<{
|
||||
serviceProvider?: ServiceProvider;
|
||||
}> = ({ status, serviceProvider, gatewayPerformance }) => {
|
||||
const color = status === 'connected' || status === 'disconnecting' ? '#21D072' : 'white';
|
||||
console.log(gatewayPerformance);
|
||||
|
||||
return (
|
||||
<Box color={color} sx={{ mb: 2 }}>
|
||||
<ConnectionStatusContent
|
||||
status={status}
|
||||
serviceProvider={serviceProvider}
|
||||
gatewayError={gatewayPerformance !== 'Good'}
|
||||
/>
|
||||
</Box>
|
||||
<>
|
||||
<Box color={color} sx={{ mb: 2 }}>
|
||||
<ConnectionStatusContent
|
||||
status={status}
|
||||
serviceProvider={serviceProvider}
|
||||
gatewayError={gatewayPerformance !== 'Good'}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -29,13 +29,9 @@ const MenuIcon = () => {
|
||||
return <CustomButton Icon={Menu} onClick={() => navigate('/menu')} />;
|
||||
};
|
||||
|
||||
const ArrowBackIcon = ({ onBack }: { onBack?: () => void }) => {
|
||||
const ArrowBackIcon = () => {
|
||||
const navigate = useNavigate();
|
||||
const handleBack = () => {
|
||||
onBack?.();
|
||||
navigate(-1);
|
||||
};
|
||||
return <CustomButton Icon={ArrowBack} onClick={handleBack} />;
|
||||
return <CustomButton Icon={ArrowBack} onClick={() => navigate(-1)} />;
|
||||
};
|
||||
|
||||
const getTitleIcon = (path: string) => {
|
||||
@@ -50,14 +46,16 @@ const getTitleIcon = (path: string) => {
|
||||
return <NymWordmark width={36} />;
|
||||
};
|
||||
|
||||
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()} />
|
||||
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>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ 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,6 +4,7 @@ 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,4 +1,5 @@
|
||||
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, Typography } from '@mui/material';
|
||||
import { Box, Button, Typography } from '@mui/material';
|
||||
import { InfoModal } from './InfoModal';
|
||||
import { CopyToClipboard } from './CopyToClipboard';
|
||||
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -1,124 +0,0 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -1,72 +0,0 @@
|
||||
#ring-expand {
|
||||
animation-name: rings-expand;
|
||||
animation-duration: 0.4s;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
#ring-one.expand {
|
||||
opacity: 0.3;
|
||||
transition: opacity 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
#ring-two.expand {
|
||||
opacity: 0.2;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
#ring-three.expand {
|
||||
opacity: 0.1;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
#ring-four.expand {
|
||||
opacity: 0.05;
|
||||
transition: opacity 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
#ring-one.contract {
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
#ring-two.contract {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
#ring-three.contract {
|
||||
opacity: 0;
|
||||
transition: opacity 0.1s;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
#ring-four.contract {
|
||||
opacity: 0;
|
||||
transition: opacity 0.1s;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
circle,
|
||||
path {
|
||||
transition: stroke 0.5s, fill 0.5s;
|
||||
}
|
||||
|
||||
.pulse {
|
||||
animation-name: pulse;
|
||||
animation-duration: 0.5s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState, useRef } from 'react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { Error } from 'src/types/error';
|
||||
import { getVersion } from '@tauri-apps/api/app';
|
||||
import { useEvents } from 'src/hooks/events';
|
||||
import { UserDefinedGateway } from 'src/types/gateway';
|
||||
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 { ConnectionStatusKind, GatewayPerformance } from '../types';
|
||||
import { ConnectionStatsItem } from '../components/ConnectionStats';
|
||||
import { ServiceProvider } from '../types/directory';
|
||||
import { ServiceProvider, Services } from '../types/directory';
|
||||
|
||||
const FORAGE_KEY = 'nym-connect-user-gateway';
|
||||
const TAURI_EVENT_STATUS_CHANGED = 'app:connection-status-changed';
|
||||
|
||||
type ModeType = 'light' | 'dark';
|
||||
|
||||
@@ -24,7 +25,6 @@ export type TClientContext = {
|
||||
gatewayPerformance: GatewayPerformance;
|
||||
selectedProvider?: ServiceProvider;
|
||||
showInfoModal: boolean;
|
||||
userDefinedGateway?: UserDefinedGateway;
|
||||
setMode: (mode: ModeType) => void;
|
||||
clearError: () => void;
|
||||
setConnectionStatus: (connectionStatus: ConnectionStatusKind) => void;
|
||||
@@ -34,7 +34,6 @@ export type TClientContext = {
|
||||
setRandomSerivceProvider: () => void;
|
||||
startConnecting: () => Promise<void>;
|
||||
startDisconnecting: () => Promise<void>;
|
||||
setUserDefinedGateway: React.Dispatch<React.SetStateAction<UserDefinedGateway>>;
|
||||
};
|
||||
|
||||
export const ClientContext = createContext({} as TClientContext);
|
||||
@@ -50,51 +49,29 @@ 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 setUserGatewayInStorage = async (gateway: UserDefinedGateway) => {
|
||||
try {
|
||||
await forage.setItem({
|
||||
key: FORAGE_KEY,
|
||||
value: gateway,
|
||||
} as any)();
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
const timerId = useRef<NodeJS.Timeout>();
|
||||
|
||||
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 flattenProviders = (services: Services) => {
|
||||
return services.reduce((a: ServiceProvider[], b) => {
|
||||
return [...a, ...b.items];
|
||||
}, []);
|
||||
};
|
||||
|
||||
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(services as ServiceProvider[]);
|
||||
if (storedUserDefinedGateway) setUserDefinedGateway(storedUserDefinedGateway);
|
||||
setServiceProviders(allServiceProviders);
|
||||
};
|
||||
|
||||
useEvents({
|
||||
onError: (e) => setError(e),
|
||||
onGatewayPerformanceChange: (performance) => setGatewayPerformance(performance),
|
||||
onStatusChange: (status) => setConnectionStatus(status),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
initialiseApp();
|
||||
}, []);
|
||||
@@ -107,6 +84,49 @@ 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');
|
||||
@@ -124,19 +144,37 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
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 setServiceProvider = async (newServiceProvider?: ServiceProvider) => {
|
||||
if (newServiceProvider) {
|
||||
await invoke('set_gateway', { gateway: newServiceProvider.gateway });
|
||||
await invoke('set_service_provider', { serviceProvider: newServiceProvider.address });
|
||||
}
|
||||
};
|
||||
|
||||
const getRandomSPFromList = (services: ServiceProvider[]) => {
|
||||
const randomSelection = services[Math.floor(Math.random() * services.length)];
|
||||
const setSpInStorage = async (sp: ServiceProvider) => {
|
||||
await forage.setItem({
|
||||
key: 'nym-connect-sp',
|
||||
value: sp,
|
||||
} as any)();
|
||||
};
|
||||
|
||||
if (shouldUseUserGateway) return { ...randomSelection, gateway: userDefinedGateway.gateway } as ServiceProvider;
|
||||
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)];
|
||||
return randomSelection;
|
||||
};
|
||||
|
||||
@@ -144,10 +182,8 @@ 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);
|
||||
@@ -172,8 +208,6 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
startDisconnecting,
|
||||
gatewayPerformance,
|
||||
setShowInfoModal,
|
||||
userDefinedGateway,
|
||||
setUserDefinedGateway,
|
||||
}),
|
||||
[
|
||||
mode,
|
||||
@@ -186,7 +220,6 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
connectedSince,
|
||||
gatewayPerformance,
|
||||
selectedProvider,
|
||||
userDefinedGateway,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ 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: () => {},
|
||||
@@ -19,7 +18,6 @@ const mockValues: TClientContext = {
|
||||
startConnecting: async () => {},
|
||||
startDisconnecting: async () => {},
|
||||
setRandomSerivceProvider: () => {},
|
||||
setUserDefinedGateway: () => {},
|
||||
};
|
||||
|
||||
export const MockProvider: FCWithChildren<{
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { listen, UnlistenFn } from '@tauri-apps/api/event';
|
||||
import { ConnectionStatusKind, GatewayPerformance } from 'src/types';
|
||||
import { Error } from 'src/types/error';
|
||||
import { TauriEvent } from 'src/types/event';
|
||||
|
||||
const TAURI_EVENT_STATUS_CHANGED = 'app:connection-status-changed';
|
||||
|
||||
export const useEvents = ({
|
||||
onError,
|
||||
onStatusChange,
|
||||
onGatewayPerformanceChange,
|
||||
}: {
|
||||
onError: (error: Error) => void;
|
||||
onStatusChange: (status: ConnectionStatusKind) => void;
|
||||
onGatewayPerformanceChange: (status: GatewayPerformance) => void;
|
||||
}) => {
|
||||
const timerId = useRef<NodeJS.Timeout>();
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten: UnlistenFn[] = [];
|
||||
|
||||
// TODO: fix typings
|
||||
listen(TAURI_EVENT_STATUS_CHANGED, (event) => {
|
||||
const { status } = event.payload as any;
|
||||
console.log(TAURI_EVENT_STATUS_CHANGED, { status, event });
|
||||
onStatusChange(status);
|
||||
})
|
||||
.then((result) => {
|
||||
unlisten.push(result);
|
||||
})
|
||||
.catch((e) => console.log(e));
|
||||
|
||||
listen('socks5-event', (e: TauriEvent) => {
|
||||
console.log(e);
|
||||
onError(e.payload);
|
||||
}).then((result) => {
|
||||
unlisten.push(result);
|
||||
});
|
||||
|
||||
listen('socks5-status-event', (e: TauriEvent) => {
|
||||
if (e.payload.message.includes('slow')) {
|
||||
onGatewayPerformanceChange('Poor');
|
||||
|
||||
if (timerId?.current) {
|
||||
clearTimeout(timerId.current);
|
||||
}
|
||||
|
||||
timerId.current = setTimeout(() => {
|
||||
onGatewayPerformanceChange('Good');
|
||||
}, 10000);
|
||||
}
|
||||
}).then((result) => {
|
||||
unlisten.push(result);
|
||||
});
|
||||
|
||||
listen('socks5-connection-fail-event', (e: TauriEvent) => {
|
||||
onError({ title: 'Connection failed', message: `${e.payload.message} - Please disconnect and reconnect.` });
|
||||
onGatewayPerformanceChange('Poor');
|
||||
}).then((result) => {
|
||||
unlisten.push(result);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlisten.forEach((unsubscribe) => unsubscribe());
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
@@ -1,14 +1,15 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { GlobalStyles } from '@mui/material';
|
||||
import { ClientContextProvider } from './context/main';
|
||||
import { ErrorFallback } from './components/Error';
|
||||
import { NymMixnetTheme } from './theme';
|
||||
import { App } from './App';
|
||||
import { AppWindowFrame } from './components/AppWindowFrame';
|
||||
import { TestAndEarnContextProvider } from './components/Growth/context/TestAndEarnContext';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { AppRoutes } from './routes';
|
||||
import { GlobalStyles } from '@mui/material';
|
||||
|
||||
const elem = document.getElementById('root');
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Stack } from '@mui/material';
|
||||
import { DateTime } from 'luxon';
|
||||
import { IpAddressAndPortModal } from 'src/components/IpAddressAndPortModal';
|
||||
@@ -10,12 +10,9 @@ 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/PowerButton';
|
||||
import { Error } from 'src/types/error';
|
||||
import { InfoModal } from 'src/components/InfoModal';
|
||||
import { PowerButton } from 'src/components/PowerButton';
|
||||
|
||||
export const Connected: FCWithChildren<{
|
||||
error?: Error;
|
||||
status: ConnectionStatusKind;
|
||||
showInfoModal: boolean;
|
||||
gatewayPerformance: GatewayPerformance;
|
||||
@@ -26,11 +23,9 @@ export const Connected: FCWithChildren<{
|
||||
busy?: boolean;
|
||||
isError?: boolean;
|
||||
serviceProvider?: ServiceProvider;
|
||||
clearError: () => void;
|
||||
onConnectClick: (status: ConnectionStatusKind) => void;
|
||||
closeInfoModal: () => void;
|
||||
}> = ({
|
||||
error,
|
||||
status,
|
||||
showInfoModal,
|
||||
gatewayPerformance,
|
||||
@@ -40,41 +35,41 @@ export const Connected: FCWithChildren<{
|
||||
busy,
|
||||
isError,
|
||||
serviceProvider,
|
||||
clearError,
|
||||
onConnectClick,
|
||||
closeInfoModal,
|
||||
}) => (
|
||||
<>
|
||||
{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}
|
||||
/>
|
||||
<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} />
|
||||
}) => {
|
||||
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>
|
||||
<ExperimentalWarning />
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
ConnectButton={
|
||||
<PowerButton
|
||||
status={status}
|
||||
busy={busy}
|
||||
onClick={onConnectClick}
|
||||
isError={isError}
|
||||
disabled={status === 'connecting' || status === 'disconnecting'}
|
||||
/>
|
||||
}
|
||||
BottomContent={
|
||||
<Stack justifyContent="space-between">
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<IpAddressAndPort label="Socks5 address" ipAddress={ipAddress} port={port} />
|
||||
</Box>
|
||||
<ExperimentalWarning />
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,12 +2,14 @@ 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 { PowerButton } from 'src/components/PowerButton/PowerButton';
|
||||
import { ConnectionButton } from 'src/components/ConnectionButton';
|
||||
import { PowerButton } from 'src/components/PowerButton';
|
||||
import { Box } from '@mui/system';
|
||||
import { ConnectionLayout } from 'src/layouts/ConnectionLayout';
|
||||
|
||||
@@ -20,32 +22,34 @@ export const Disconnected: FCWithChildren<{
|
||||
serviceProvider?: ServiceProvider;
|
||||
clearError: () => void;
|
||||
onConnectClick: (status: ConnectionStatusKind) => void;
|
||||
}> = ({ 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>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}> = ({ 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>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -47,8 +47,6 @@ export const ConnectionPage = () => {
|
||||
if (context.connectionStatus === 'connected')
|
||||
return (
|
||||
<Connected
|
||||
error={context.error}
|
||||
clearError={context.clearError}
|
||||
status={context.connectionStatus}
|
||||
showInfoModal={context.showInfoModal}
|
||||
busy={busy}
|
||||
|
||||
@@ -19,8 +19,8 @@ export const CompatibleApps = () => (
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
|
||||
<Box sx={{ mb: 4 }}>
|
||||
{appsSchema.messagingApps.map((app) => (
|
||||
<Typography variant="body2" color="grey.400" sx={{ mb: 2 }} key={app}>
|
||||
{appsSchema.messagingApps.map((app, i) => (
|
||||
<Typography variant="body2" color="grey.400" sx={{ mb: 2 }} key={i}>
|
||||
{app}
|
||||
</Typography>
|
||||
))}
|
||||
@@ -32,8 +32,8 @@ export const CompatibleApps = () => (
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
|
||||
<Box sx={{ mb: 4 }}>
|
||||
{appsSchema.wallets.map((wallet) => (
|
||||
<Typography variant="body2" color="grey.400" sx={{ mb: 2 }} key={wallet}>
|
||||
{appsSchema.wallets.map((wallet, i) => (
|
||||
<Typography variant="body2" color="grey.400" sx={{ mb: 2 }} key={i}>
|
||||
{wallet}
|
||||
</Typography>
|
||||
))}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import React, { ChangeEvent, useState } from 'react';
|
||||
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
|
||||
import { Box, FormControl, FormControlLabel, FormHelperText, Link, Stack, Switch, Typography } from '@mui/material';
|
||||
import { useClientContext } from 'src/context/main';
|
||||
import { ConnectionStatusKind } from 'src/types';
|
||||
import { AppVersion } from 'src/components/AppVersion';
|
||||
|
||||
export const Settings = () => {
|
||||
const { userDefinedGateway, setUserDefinedGateway } = useClientContext();
|
||||
const [gatewayKey, setGatewayKey] = useState<string | undefined>(userDefinedGateway?.gateway);
|
||||
|
||||
const handleIsValidGatewayKey = (isValid: boolean) => {
|
||||
let gateway: string | undefined;
|
||||
|
||||
if (isValid) {
|
||||
gateway = gatewayKey;
|
||||
}
|
||||
|
||||
setUserDefinedGateway((current) => ({ ...current, gateway }));
|
||||
};
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setUserDefinedGateway((current) => ({ ...current, isActive: e.target.checked }));
|
||||
};
|
||||
|
||||
const { connectionStatus } = useClientContext();
|
||||
|
||||
return (
|
||||
<Box height="100%">
|
||||
<Stack justifyContent="space-between" height="100%">
|
||||
<Box>
|
||||
<Typography fontWeight="bold" variant="body2" mb={1}>
|
||||
Select your Gateway
|
||||
</Typography>
|
||||
<Typography color="grey.300" variant="body2" mb={2}>
|
||||
Use a gateway of your choice
|
||||
</Typography>
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={userDefinedGateway?.isActive}
|
||||
onChange={handleChange}
|
||||
disabled={connectionStatus === ConnectionStatusKind.connected}
|
||||
size="small"
|
||||
sx={{ ml: 1 }}
|
||||
/>
|
||||
}
|
||||
label={userDefinedGateway?.isActive ? 'On' : 'Off'}
|
||||
/>
|
||||
{connectionStatus === ConnectionStatusKind.connected && (
|
||||
<FormHelperText sx={{ m: 0, my: 1 }}>This setting is disabled during an active connection</FormHelperText>
|
||||
)}
|
||||
{userDefinedGateway?.isActive && (
|
||||
<IdentityKeyFormField
|
||||
size="small"
|
||||
placeholder="Gateway identity key"
|
||||
onChanged={setGatewayKey}
|
||||
initialValue={gatewayKey}
|
||||
onValidate={handleIsValidGatewayKey}
|
||||
sx={{ mt: 1 }}
|
||||
disabled={connectionStatus === 'connected' || !userDefinedGateway?.isActive}
|
||||
/>
|
||||
)}
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="body2" mb={3}>
|
||||
To find a gateway go to the{' '}
|
||||
<Link
|
||||
underline="none"
|
||||
target="_blank"
|
||||
href="https://explorer.nymtech.net/network-components/gateways"
|
||||
sx={{ cursor: 'pointer' }}
|
||||
color="nym.cta"
|
||||
>
|
||||
Network Explorer
|
||||
</Link>
|
||||
</Typography>
|
||||
<AppVersion />
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Apps, HelpOutline, Settings } from '@mui/icons-material';
|
||||
import { Apps, HelpOutline } 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,25 +7,24 @@ 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 = () => (
|
||||
<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>
|
||||
);
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
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 = () => (
|
||||
<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>
|
||||
);
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,12 +2,12 @@ import React from 'react';
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import { Box } from '@mui/material';
|
||||
import { DateTime } from 'luxon';
|
||||
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';
|
||||
import { Disconnected } from 'src/pages/connection/Disconnected';
|
||||
import { Connected } from 'src/pages/connection/Connected';
|
||||
import { ConnectionStatusKind } from 'src/types';
|
||||
|
||||
export default {
|
||||
title: 'App/Flow',
|
||||
@@ -87,7 +87,6 @@ export const Mock: ComponentStory<typeof AppWindowFrame> = () => {
|
||||
return (
|
||||
<AppWindowFrame>
|
||||
<Connected
|
||||
clearError={() => {}}
|
||||
gatewayPerformance="Good"
|
||||
showInfoModal={false}
|
||||
closeInfoModal={() => undefined}
|
||||
|
||||
@@ -15,10 +15,11 @@ 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={() => undefined}
|
||||
closeInfoModal={() => {
|
||||
return 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 { ConnectionStatusKind } from 'src/types';
|
||||
import { ConnectionButton } from '../components/ConnectionButton';
|
||||
import { ConnectionStatusKind } from 'src/types';
|
||||
|
||||
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 { Disconnected } from 'src/pages/connection/Disconnected';
|
||||
import { ConnectionStatusKind } from '../types';
|
||||
import { Disconnected } from 'src/pages/connection/Disconnected';
|
||||
|
||||
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/PowerButton';
|
||||
import { PowerButton } from 'src/components/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} disabled />
|
||||
<PowerButton status={ConnectionStatusKind.connecting} />
|
||||
);
|
||||
|
||||
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} disabled />
|
||||
<PowerButton status={ConnectionStatusKind.disconnecting} />
|
||||
);
|
||||
|
||||
export const Disabled: ComponentStory<typeof PowerButton> = () => (
|
||||
<PowerButton status={ConnectionStatusKind.disconnected} disabled />
|
||||
<PowerButton status={ConnectionStatusKind.connecting} disabled />
|
||||
);
|
||||
|
||||
Vendored
-1
@@ -29,7 +29,6 @@ declare module '@mui/material/styles' {
|
||||
*/
|
||||
interface NymPalette {
|
||||
highlight: string;
|
||||
cta: string;
|
||||
success: string;
|
||||
warning: string;
|
||||
info: string;
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
const nymPalette: NymPalette = {
|
||||
/** emphasises important elements */
|
||||
highlight: '#21D072',
|
||||
cta: '#FB6E4E',
|
||||
success: '#21D073',
|
||||
info: '#60D7EF',
|
||||
warning: '#FFE600',
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface UserDefinedGateway {
|
||||
isActive: boolean;
|
||||
gateway?: string;
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
@@ -2,17 +2,6 @@
|
||||
|
||||
## UNRELEASED
|
||||
|
||||
## [nym-wallet-v1.1.9](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.9) (2023-02-14)
|
||||
|
||||
- Allow more flexibility for user when setting passwords ([#2993])
|
||||
- User feedback on weak passwords ([#2993])
|
||||
- User no longer has to copy mnemonic to continune account creation ([#2948])
|
||||
- Updated instructional steps for creating accounts with a password ([#2962])
|
||||
|
||||
[#2948]: https://github.com/nymtech/nym/issues/2948
|
||||
[#2993]: https://github.com/nymtech/nym/issues/2993
|
||||
[#2962]: https://github.com/nymtech/nym/issues/2962
|
||||
|
||||
## [nym-wallet-v1.1.8](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.8) (2023-01-24)
|
||||
|
||||
- Fix delegations sorting ([#2885])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nymproject/nym-wallet-app",
|
||||
"version": "1.1.9",
|
||||
"version": "1.1.8",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -52,8 +52,7 @@
|
||||
"string-to-color": "^2.2.2",
|
||||
"use-clipboard-copy": "^0.2.0",
|
||||
"uuid": "^8.3.2",
|
||||
"yup": "^0.32.9",
|
||||
"zxcvbn": "^4.4.2"
|
||||
"yup": "^0.32.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.15.0",
|
||||
@@ -77,7 +76,6 @@
|
||||
"@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",
|
||||
@@ -122,4 +120,4 @@
|
||||
"webpack-favicons": "^1.3.8",
|
||||
"webpack-merge": "^5.8.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym_wallet"
|
||||
version = "1.1.9"
|
||||
version = "1.1.8"
|
||||
description = "Nym Native Wallet"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-wallet",
|
||||
"version": "1.1.9"
|
||||
"version": "1.1.8"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
@@ -14,13 +14,7 @@
|
||||
"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",
|
||||
@@ -33,6 +27,7 @@
|
||||
"macOS": {
|
||||
"frameworks": [],
|
||||
"minimumSystemVersion": "",
|
||||
|
||||
"exceptionDomain": "",
|
||||
"signingIdentity": "Developer ID Application: Nym Technologies SA (VW5DZLFHM5)",
|
||||
"entitlements": null
|
||||
@@ -45,9 +40,7 @@
|
||||
},
|
||||
"updater": {
|
||||
"active": true,
|
||||
"endpoints": [
|
||||
"https://nymtech.net/.wellknown/wallet/updater.json"
|
||||
],
|
||||
"endpoints": ["https://nymtech.net/.wellknown/wallet/updater.json"],
|
||||
"dialog": true,
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IENCNzQ2M0E5N0VFODE2NApSV1JrZ2U2WE9rYTNETTg1OTBKdE5uWUEra0hML2syOVUvQ2lxZmFZRzZ1T3NWbGM0eVRzUTVhVwo="
|
||||
},
|
||||
@@ -74,4 +67,4 @@
|
||||
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,11 @@ import { SimpleModal } from '../Modals/SimpleModal';
|
||||
import { Warning } from '../Warning';
|
||||
|
||||
const passwordCreationSteps = [
|
||||
'Log out from the wallet',
|
||||
'Log out of your wallet',
|
||||
'Sign in using “Sign in with mnemonic” button',
|
||||
'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',
|
||||
'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',
|
||||
];
|
||||
|
||||
// TODO add the link href value
|
||||
|
||||
@@ -10,6 +10,7 @@ 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';
|
||||
@@ -29,16 +30,16 @@ const importAccountSteps = [
|
||||
];
|
||||
|
||||
const MnemonicStep = ({ mnemonic, onNext, onBack }: { mnemonic: string; onNext: () => void; onBack: () => void }) => {
|
||||
const [confirmed, setConfirmed] = useState(false);
|
||||
const { copy, copied } = useClipboard({ copiedTimeout: 5000 });
|
||||
return (
|
||||
<Box sx={{ mt: 1 }}>
|
||||
<DialogContent>
|
||||
<Mnemonic mnemonic={mnemonic} handleConfirmed={setConfirmed} confirmed={confirmed} />
|
||||
<Mnemonic mnemonic={mnemonic} handleCopy={copy} copied={copied} />
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ p: 3, pt: 0, gap: 2 }}>
|
||||
<StyledBackButton onBack={onBack} />
|
||||
<Button disabled={!confirmed} fullWidth disableElevation variant="contained" size="large" onClick={onNext}>
|
||||
Continue
|
||||
<Button disabled={!copied} fullWidth disableElevation variant="contained" size="large" onClick={onNext}>
|
||||
I saved my mnemonic
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Box>
|
||||
|
||||
@@ -11,12 +11,15 @@ 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,
|
||||
@@ -69,7 +72,7 @@ export const MnemonicModal = () => {
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Mnemonic mnemonic={accountMnemonic.value} />
|
||||
<Mnemonic mnemonic={accountMnemonic.value} handleCopy={copy} copied={copied} />
|
||||
)}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
|
||||
@@ -40,7 +40,7 @@ export const ClientAddressDisplay: FC<ClientAddressProps & { address?: string }>
|
||||
)}
|
||||
|
||||
<AddressTooltip address={address} visible={!showEntireAddress}>
|
||||
<Typography variant="body2" component="span" sx={{ mr: 1, color: 'text.primary', fontWeight: 400 }}>
|
||||
<Typography data-testid="wallet-address" variant="body2" component="span" sx={{ mr: 1, color: 'text.primary', fontWeight: 400 }}>
|
||||
{showEntireAddress ? address || '' : splice(6, address)}
|
||||
</Typography>
|
||||
</AddressTooltip>
|
||||
|
||||
@@ -1,40 +1,33 @@
|
||||
import React from 'react';
|
||||
import { Box, Checkbox, FormControlLabel, Stack, TextField, Typography } from '@mui/material';
|
||||
import { Title } from 'src/pages/auth/components/heading';
|
||||
import { Button, Stack, TextField, Typography } from '@mui/material';
|
||||
import { Check, ContentCopySharp } from '@mui/icons-material';
|
||||
import { Warning } from './Warning';
|
||||
import { MnemonicModal } from './Accounts/modals/MnemonicModal';
|
||||
|
||||
export const Mnemonic = ({
|
||||
mnemonic,
|
||||
confirmed,
|
||||
withTitle,
|
||||
handleConfirmed,
|
||||
copied,
|
||||
handleCopy,
|
||||
}: {
|
||||
mnemonic: string;
|
||||
confirmed?: boolean;
|
||||
withTitle?: boolean;
|
||||
handleConfirmed?: (confirmed: boolean) => void;
|
||||
copied: boolean;
|
||||
handleCopy: (text?: string) => void;
|
||||
}) => (
|
||||
<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>
|
||||
<Stack spacing={2} alignItems="center">
|
||||
<Warning>
|
||||
<Typography sx={{ textAlign: 'center' }} data-testid="below24word">
|
||||
Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the future
|
||||
</Typography>
|
||||
</Warning>
|
||||
<TextField
|
||||
label="Mnemonic"
|
||||
id="mnemonicPhrase"
|
||||
type="input"
|
||||
value={mnemonic}
|
||||
multiline
|
||||
autoFocus={false}
|
||||
fullWidth
|
||||
inputProps={{
|
||||
inputProps={{
|
||||
style: {
|
||||
height: '160px',
|
||||
},
|
||||
@@ -47,11 +40,20 @@ export const Mnemonic = ({
|
||||
}}
|
||||
/>
|
||||
|
||||
{handleConfirmed && (
|
||||
<FormControlLabel
|
||||
label="I saved my mnemonic"
|
||||
control={<Checkbox checked={confirmed} onChange={(_, checked) => handleConfirmed(checked)} />}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
data-testid="copyMnemonic"
|
||||
color="inherit"
|
||||
disableElevation
|
||||
size="large"
|
||||
onClick={() => {
|
||||
handleCopy(mnemonic);
|
||||
}}
|
||||
sx={{
|
||||
width: 250,
|
||||
}}
|
||||
endIcon={!copied ? <ContentCopySharp /> : <Check color="success" />}
|
||||
>
|
||||
Copy mnemonic
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -103,6 +103,7 @@ export const Nav = () => {
|
||||
<ListItem
|
||||
disableGutters
|
||||
key={label}
|
||||
data-testid={label}
|
||||
onClick={onClick}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
|
||||
@@ -16,7 +16,7 @@ const NetworkItem: FCWithChildren<{ title: string; isSelected: boolean; onSelect
|
||||
isSelected,
|
||||
onSelect,
|
||||
}) => (
|
||||
<ListItem button onClick={onSelect}>
|
||||
<ListItem button onClick={onSelect} data-testid={title}>
|
||||
<ListItemIcon>{isSelected && <CheckSharp color="success" />}</ListItemIcon>
|
||||
<ListItemText>{title}</ListItemText>
|
||||
</ListItem>
|
||||
@@ -38,6 +38,7 @@ export const NetworkSelector = () => {
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
data-testid="networkEnv"
|
||||
variant="text"
|
||||
color="inherit"
|
||||
sx={{ color: 'text.primary', fontSize: 14 }}
|
||||
|
||||
@@ -35,9 +35,7 @@ export const inputValidationSchema = Yup.object().shape({
|
||||
.test('Is valid operator cost value', (value, ctx) => {
|
||||
const stringValueToNumber = Math.round(Number(value));
|
||||
|
||||
if (isLessThan(stringValueToNumber, 0))
|
||||
return ctx.createError({ message: 'Operator cost must be a valid number' });
|
||||
|
||||
return true;
|
||||
if (isGreaterThan(stringValueToNumber, -1) && isLessThan(stringValueToNumber, 101)) return true;
|
||||
return ctx.createError({ message: 'Operator cost must be a valid number' });
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -21,6 +21,8 @@ export const MnemonicInput: FCWithChildren<{
|
||||
autoFocus
|
||||
fullWidth
|
||||
multiline={showMnemonic}
|
||||
inputProps={{
|
||||
"data-testid": "inputMnemonic" }}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
sx={{
|
||||
'input::-webkit-textfield-decoration-container': {
|
||||
@@ -29,6 +31,7 @@ export const MnemonicInput: FCWithChildren<{
|
||||
}}
|
||||
/>
|
||||
<FormControlLabel
|
||||
data-testid="Reveal Mnemonic"
|
||||
control={<Checkbox checked={Boolean(showMnemonic)} onChange={() => setShowMnemonic((show) => !show)} />}
|
||||
label="Reveal my mnemonic"
|
||||
/>
|
||||
@@ -68,6 +71,9 @@ export const PasswordInput: FCWithChildren<{
|
||||
),
|
||||
}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
inputProps={{
|
||||
"data-testid": label,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{error && <Error message={error} />}
|
||||
|
||||
@@ -2,11 +2,11 @@ import React from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
|
||||
export const Title = ({ title }: { title: string }) => (
|
||||
<Typography sx={{ color: 'common.white', fontWeight: 600, fontSize: 20 }}>{title}</Typography>
|
||||
<Typography data-testid={title} sx={{ color: 'common.white', fontWeight: 600, fontSize: 20 }}>{title}</Typography>
|
||||
);
|
||||
|
||||
export const Subtitle = ({ subtitle }: { subtitle: string }) => (
|
||||
<Typography sx={{ color: 'common.white', textAlign: 'center', maxWidth: 450 }}>{subtitle}</Typography>
|
||||
<Typography data-testid={subtitle} sx={{ color: 'common.white', textAlign: 'center', maxWidth: 450 }}>{subtitle}</Typography>
|
||||
);
|
||||
|
||||
export const SubtitleSlick = ({ subtitle }: { subtitle: string }) => (
|
||||
|
||||
@@ -1,61 +1,52 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import React from 'react';
|
||||
import zxcvbn, { ZXCVBNScore } from 'zxcvbn';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
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 = {
|
||||
4: 'success' as 'success',
|
||||
3: 'success' as 'success',
|
||||
2: 'warning' as 'warning',
|
||||
1: 'error' as 'error',
|
||||
0: 'error' as 'error',
|
||||
init: 'inherit' as 'inherit',
|
||||
weak: 'error' as 'error',
|
||||
medium: 'warning' as 'warning',
|
||||
strong: 'success' as 'success',
|
||||
};
|
||||
|
||||
const getText = (score: ZXCVBNScore) => {
|
||||
switch (score) {
|
||||
case 4:
|
||||
return 'Very strong password';
|
||||
case 3:
|
||||
const getText = (strength: TStrength) => {
|
||||
switch (strength) {
|
||||
case 'strong':
|
||||
return 'Strong password';
|
||||
case 2:
|
||||
return 'Average password';
|
||||
case 1:
|
||||
case 'medium':
|
||||
return 'Medium strength password';
|
||||
case 'weak':
|
||||
return 'Weak password';
|
||||
case 0:
|
||||
return 'Very weak password';
|
||||
default:
|
||||
return '';
|
||||
return 'Password strength';
|
||||
}
|
||||
};
|
||||
|
||||
const getColor = (score: ZXCVBNScore) => {
|
||||
switch (score) {
|
||||
case 4:
|
||||
const getTextColor = (strength: TStrength) => {
|
||||
switch (strength) {
|
||||
case 'strong':
|
||||
return 'success.main';
|
||||
case 3:
|
||||
return 'success.main';
|
||||
case 2:
|
||||
case 'medium':
|
||||
return 'warning.main';
|
||||
case 1:
|
||||
return 'error.main';
|
||||
case 0:
|
||||
case 'weak':
|
||||
return 'error.main';
|
||||
default:
|
||||
return 'grey.500';
|
||||
}
|
||||
};
|
||||
|
||||
const getPasswordStrength = (score: ZXCVBNScore) => {
|
||||
switch (score) {
|
||||
case 4:
|
||||
const getPasswordStrength = (strength: TStrength) => {
|
||||
switch (strength) {
|
||||
case 'strong':
|
||||
return 100;
|
||||
case 3:
|
||||
return 75;
|
||||
case 2:
|
||||
case 'medium':
|
||||
return 50;
|
||||
case 1:
|
||||
return 25;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -63,32 +54,47 @@ const getPasswordStrength = (score: ZXCVBNScore) => {
|
||||
|
||||
export const PasswordStrength = ({
|
||||
password,
|
||||
withWarnings,
|
||||
handleIsSafePassword,
|
||||
onChange,
|
||||
}: {
|
||||
password: string;
|
||||
withWarnings?: boolean;
|
||||
handleIsSafePassword: (isSafe: boolean) => void;
|
||||
onChange: (isStrong: boolean) => void;
|
||||
}) => {
|
||||
const result = zxcvbn(password);
|
||||
const [strength, setStrength] = useState<TStrength>('init');
|
||||
|
||||
handleIsSafePassword(result.score > 1);
|
||||
useEffect(() => {
|
||||
if (password.length === 0) {
|
||||
setStrength('init');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!password.length) return null;
|
||||
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]);
|
||||
|
||||
return (
|
||||
<Stack spacing={0.5}>
|
||||
<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>
|
||||
)}
|
||||
<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>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
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,
|
||||
};
|
||||
@@ -53,7 +53,7 @@ export const WordTiles = ({
|
||||
return (
|
||||
<Grid container spacing={3} justifyContent="center">
|
||||
{mnemonicWords.map(({ name, index, disabled }) => (
|
||||
<Grid item xs={2} key={index} onClick={() => onClick?.({ name, index })}>
|
||||
<Grid item xs={2} key={index} onClick={() => onClick?.({ name, index })} data-testid="mnemonicWordTile">
|
||||
<WordTile
|
||||
mnemonicWord={name}
|
||||
index={showIndex ? index : undefined}
|
||||
@@ -79,7 +79,7 @@ const HiddenWord = ({ mnemonicWord }: { mnemonicWord: THiddenMnemonicWord }) =>
|
||||
</Box>
|
||||
</Fade>
|
||||
</Box>
|
||||
<Typography>{mnemonicWord.index}.</Typography>
|
||||
<Typography data-testid="wordIndex">{mnemonicWord.index}.</Typography>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import { AuthProvider } from 'src/context';
|
||||
import { AuthRoutes } from 'src/routes/auth';
|
||||
|
||||
export const Auth = () => (
|
||||
<AuthProvider>
|
||||
<AuthRoutes />
|
||||
</AuthProvider>
|
||||
);
|
||||
@@ -20,6 +20,7 @@ export const ConfirmMnemonic = () => {
|
||||
<Subtitle subtitle="Enter the mnemonic you wish to create a password for" />
|
||||
<MnemonicInput mnemonic={localMnemonic} onUpdateMnemonic={(mnc) => setLocalMnemonic(mnc)} error={error} />
|
||||
<Button
|
||||
data-testid="nextToPasswordCreation"
|
||||
size="large"
|
||||
variant="contained"
|
||||
fullWidth
|
||||
@@ -37,6 +38,7 @@ export const ConfirmMnemonic = () => {
|
||||
Next
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="backToMnemonicSignIn"
|
||||
size="large"
|
||||
color="inherit"
|
||||
fullWidth
|
||||
|
||||
@@ -9,8 +9,9 @@ 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();
|
||||
|
||||
@@ -47,23 +48,27 @@ export const ConnectPassword = () => {
|
||||
onUpdatePassword={(pswd) => setPassword(pswd)}
|
||||
label="Password"
|
||||
autoFocus
|
||||
data-testid="Password"
|
||||
/>
|
||||
<PasswordStrength password={password} handleIsSafePassword={setIsSafePassword} withWarnings />
|
||||
<PasswordStrength password={password} onChange={(isStrong) => setIsStrongPassword(isStrong)} />
|
||||
</>
|
||||
<PasswordInput
|
||||
password={confirmedPassword}
|
||||
onUpdatePassword={(pswd) => setConfirmedPassword(pswd)}
|
||||
label="Confirm password"
|
||||
data-testid="Confirm Password"
|
||||
/>
|
||||
<Button
|
||||
data-testid="createPasswordButton"
|
||||
size="large"
|
||||
variant="contained"
|
||||
disabled={password !== confirmedPassword || password.length === 0 || isLoading || !isSafePassword}
|
||||
disabled={password !== confirmedPassword || password.length === 0 || !isStrongPassword || isLoading}
|
||||
onClick={storePassword}
|
||||
>
|
||||
{isLoading ? <CircularProgress size={25} /> : 'Create password'}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="backToStep1PasswordCreation"
|
||||
size="large"
|
||||
color="inherit"
|
||||
onClick={() => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user