Compare commits

..

73 Commits

Author SHA1 Message Date
benedetta davico aa833310da Update validator-api-tests.yml 2023-02-22 13:05:25 +01:00
benedettadavico 17ed80018f adding tests for circulating endpoints 2023-02-06 11:22:52 +01:00
benedettadavico cdfcb065b7 merge develop into feature/wallet-tests 2023-02-06 09:41:23 +01:00
Mark Sinclair fd1c0ae62b Update build-and-upload-binaries-ci.yml 2023-02-03 12:03:13 +00:00
Mark Sinclair bd7399091b Update build-and-upload-binaries-ci.yml 2023-02-03 11:57:32 +00:00
Mark Sinclair 00fad44e2e Update build-and-upload-binaries-ci.yml 2023-02-03 11:46:07 +00:00
Mark Sinclair ea33c332ee GitHub Actions: add action to build and upload binaries to CI server 2023-02-03 11:35:03 +00:00
Bogdan-Ștefan Neacșu b39f8af8d0 Fix flaky dkg test 2023-02-03 12:29:21 +02:00
dependabot[bot] a400463b7e build(deps): bump ua-parser-js in /nym-wallet/webdriver (#2909)
Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 0.7.28 to 0.7.33.
- [Release notes](https://github.com/faisalman/ua-parser-js/releases)
- [Changelog](https://github.com/faisalman/ua-parser-js/blob/master/changelog.md)
- [Commits](https://github.com/faisalman/ua-parser-js/compare/0.7.28...0.7.33)

---
updated-dependencies:
- dependency-name: ua-parser-js
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-03 10:00:09 +00:00
dependabot[bot] 82928af64c build(deps): bump http-cache-semantics in /nym-wallet/webdriver (#2960)
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/commits)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-03 09:59:08 +00:00
dependabot[bot] c5891f546c build(deps): bump http-cache-semantics from 4.1.0 to 4.1.1 (#2961)
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/commits)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-03 09:58:34 +00:00
Tommy Verrall 580b677489 Binary checker, introduction 2023-02-03 08:46:40 -01:00
Jędrzej Stuczyński 2093789c5c Added an option to set custom 'host' for the native client (#2939)
* Added an option to set custom 'host' for the native client

* Changelog entry
2023-02-02 16:38:39 +00:00
Pierre Dommerc d09864b99f build(nc-android): prepare for apk release (#2943)
* chore(nc-android): prepare for production build

* refactor(nc-android): remove dead code

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

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

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

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

* fix(nc-android): post rebase changes
2023-02-02 15:18:58 +01:00
Bogdan-Ștefan Neacşu f6e8278592 Fix typo during merge back to develop (#2956) 2023-02-02 13:55:58 +02:00
cgi-bin/ b085a8a636 typo: electrum (#2954) 2023-02-02 09:34:08 +00:00
farbanas e6ce531aeb fix: formatting 2023-02-01 17:16:53 +01:00
farbanas 1a860eb3f5 fix: wrong parameter type for addresses in generator commands (should be AccountId instead of String) 2023-02-01 12:45:51 +01:00
Fran Arbanas 09cfd9ff05 Update CHANGELOG.md 2023-01-31 14:42:16 +00:00
Bogdan-Ștefan Neacșu 2c88ca137a Resolve merge conflict 2023-01-31 16:19:34 +02:00
farbanas ca3bfc859a merge conflicts 2023-01-31 15:09:35 +01:00
benedettadavico 529db99b8e Merge branch 'develop' into feature/wallet-tests 2023-01-26 11:59:07 +01:00
benedettadavico fd4f083742 adding missing endpoints 2023-01-26 11:58:41 +01:00
benedettadavico 06256e512d adding tests 2023-01-24 15:18:30 +01:00
benedettadavico 6b4e915cd6 Merge branch 'release/v1.1.7' into feature/wallet-tests 2023-01-24 13:20:36 +01:00
benedettadavico 19cdb7f629 adding tests, cleaning up 2023-01-18 10:52:57 +01:00
benedettadavico 029f8904fe Merge branch 'develop' of https://github.com/nymtech/nym into feature/wallet-tests 2023-01-17 16:12:58 +01:00
benedettadavico 5f852e04a0 run diff envs 2023-01-06 17:49:23 +01:00
benedettadavico 365b955c90 Merge branch 'release/v1.1.5' into feature/wallet-tests 2023-01-06 17:05:22 +01:00
benedettadavico f14a87809e editing tests 2023-01-06 17:04:22 +01:00
benedettadavico a18eec2fe5 fix renaming merge conflicts 2023-01-04 10:50:00 +01:00
benedettadavico 3c99644420 conflicts 2023-01-04 10:45:57 +01:00
benedettadavico d6e6369389 Merge branch 'release/v1.1.4' of https://github.com/nymtech/nym into feature/wallet-tests 2022-12-21 17:13:01 +01:00
Fouad eff1f383c3 Feature/node settings apy playground (#1677) (#2738)
* initial ui for test my node

use svg for node path

add stories for test my node

* add initial rewards calculation

* update validation for rewards playground

* init playground with default values

* get node uptime

* get mixnode reward estimation

* calculate saturation

calculate stake saturation

* Make ComputeRewardEstParam derive Debug

* set active set to be always true

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
2022-12-21 12:56:40 +00:00
benedettadavico a192e0d745 Add family to mixnode detailed 2022-12-08 17:52:20 +01:00
benedettadavico 6168ac9e9b Merge branch 'feature/wallet-tests' of https://github.com/nymtech/nym into feature/wallet-tests 2022-12-08 17:39:54 +01:00
benedettadavico f7d96042de Merge branch 'develop' of https://github.com/nymtech/nym into feature/wallet-tests 2022-12-08 17:38:47 +01:00
tommy 353ccbe258 refine... 2022-11-16 17:07:06 +01:00
tommy 6b74a1f091 add ignore - for some reason it wasn't there 2022-11-16 15:48:29 +01:00
tommy 2f191f50ee remove 2022-11-16 13:24:28 +01:00
tommy 0de1196f85 put in placeholder for things 2022-11-16 13:24:05 +01:00
tommy 78c1cc05e1 remove the check for the wallet 2022-11-16 12:42:08 +01:00
tommy ee8101db04 run formatter and remove duplicate deleteFile 2022-11-16 12:36:09 +01:00
benedettadavico cd6c68907c WIP 2022-11-14 13:28:34 +01:00
benedettadavico 452e9ed637 WIP; debugging issues 2022-11-11 17:44:03 +01:00
benedettadavico eeeb8052b6 Merge branch 'release/v1.1.0' into feature/wallet-tests 2022-11-11 10:23:49 +01:00
benedettadavico bc79ee0cad WIP 2022-11-10 18:29:16 +01:00
benedettadavico aa5cbd06d4 WIP 2022-11-10 11:58:08 +01:00
benedettadavico 15cdf579b2 WIP 2022-11-10 11:57:13 +01:00
benedettadavico caf9429431 Merge branch 'release/v1.1.0', remote-tracking branch 'origin' into feature/wallet-tests 2022-11-10 10:30:06 +01:00
benedettadavico 708782fdf9 WIP 2022-11-10 10:27:11 +01:00
benedettadavico 3fc11d8bef WIP 2022-11-09 18:56:27 +01:00
benedettadavico 2eadda2295 WIP 2022-11-09 16:51:32 +01:00
benedettadavico 9434cb266b WIP 2022-11-09 14:28:49 +01:00
benedettadavico 8f49db1150 WIP 2022-11-09 10:57:24 +01:00
benedettadavico c45e8da43d Merge branch 'develop-with-release-1.1.0-merged-in' into feature/validator-api-tests 2022-11-09 10:15:54 +01:00
benedettadavico 12cc49a734 WIP 2022-11-09 10:05:47 +01:00
benedettadavico 7e56a9e88c WIP 2022-11-08 16:22:39 +01:00
benedettadavico 9790009eac WIP 2022-11-08 12:30:27 +01:00
benedettadavico 379d593daf Updating more tests 2022-11-07 18:37:31 +01:00
benedettadavico ce75b99b6f Merge branch 'release/v1.1.0' into feature/validator-api-tests 2022-11-07 17:36:21 +01:00
benedettadavico bcb7c41fd7 Updating validator api tests for v2 contracts 2022-11-07 17:31:25 +01:00
benedettadavico bb091ce47f Updating validator api tests for v2 contracts 2022-11-07 17:28:13 +01:00
benedettadavico effed4d7d6 Merge branch 'release/v1.1.0' into feature/validator-api-tests 2022-11-07 09:36:16 +01:00
benedettadavico d480ddb133 fixing failing tests 2022-08-15 15:20:23 +02:00
benedettadavico b119820591 Clean up 2022-08-15 09:25:28 +02:00
benedettadavico e128949dc2 Clean up 2022-08-13 20:40:08 +02:00
benedettadavico 9499b987e5 possible approach to validating address length and proxy type 2022-08-13 20:31:50 +02:00
benedettadavico d6ac786295 adding tests 2022-08-12 15:51:23 +02:00
tommy 4d09d9c3db remove 1-2-1 mapping 2022-08-12 13:30:27 +02:00
tommy 8c9044adf3 remove the need to map to type 2022-08-12 13:26:46 +02:00
tommy 472085ca52 Fix up look sharp
- added missing .git files
- fixed paths
- run the linter
2022-08-12 11:18:17 +02:00
benedettadavico 2f089e80ff adding onto the validator-api tests 2022-08-12 10:12:57 +02:00
174 changed files with 12017 additions and 2881 deletions
Vendored
BIN
View File
Binary file not shown.
@@ -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/"
+32
View File
@@ -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
View File
@@ -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
View File
@@ -3438,7 +3438,7 @@ dependencies = [
[[package]]
name = "nym-api"
version = "1.1.9"
version = "1.1.8"
dependencies = [
"anyhow",
"async-trait",
+1 -1
View File
@@ -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 -1
View File
@@ -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"
+2 -2
View File
@@ -31,7 +31,7 @@ pub(crate) struct Init {
force_register_gateway: bool,
/// Comma separated list of rest endpoints of the nyxd validators
#[clap(long, alias = "nymd_validators", value_delimiter = ',', 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
+2 -2
View File
@@ -23,7 +23,7 @@ pub(crate) struct Run {
id: String,
/// Comma separated list of rest endpoints of the nyxd validators
#[clap(long, alias = "nymd_validators", value_delimiter = ',', 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 -1
View File
@@ -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"
+2 -2
View File
@@ -43,7 +43,7 @@ pub(crate) struct Init {
force_register_gateway: bool,
/// Comma separated list of rest endpoints of the nyxd validators
#[clap(long, alias = "nymd_validators", value_delimiter = ',', 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
+2 -2
View File
@@ -43,7 +43,7 @@ pub(crate) struct Run {
gateway: Option<identity::PublicKey>,
/// Comma separated list of rest endpoints of the nyxd validators
#[clap(long, alias = "nymd_validators", value_delimiter = ',', 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 -1
View File
@@ -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
-10
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nym/network-explorer",
"version": "1.0.5",
"version": "1.0.4",
"private": true,
"license": "Apache-2.0",
"dependencies": {
+1 -11
View File
@@ -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
View File
@@ -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>",
+2 -3
View File
@@ -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
+2 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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>",
+1 -5
View File
@@ -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.
+4 -59
View File
@@ -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(),
)))
}
+13 -3
View File
@@ -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();
});
+2 -7
View File
@@ -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
View File
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");
});
});
@@ -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;
}
}
+4 -2
View File
@@ -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`,
});
+4 -4
View File
@@ -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;
};
+32 -28
View File
@@ -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;
}
};
+1 -1
View File
@@ -20,6 +20,6 @@
"typeRoots": ["node_modules/@types"],
"alwaysStrict": true
},
"include": ["src/**/*", "tests/functional_test/*/*"],
"include": ["src/**/*", "functional_test/*/*"],
"exclude": ["unit_test/**/*"]
}
+23
View File
@@ -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 }
}
}
+36
View File
@@ -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>,
}
-14
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nym/nym-connect",
"version": "1.1.9",
"version": "1.1.8",
"main": "index.js",
"license": "MIT",
"scripts": {
+1 -3
View File
@@ -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"}
-1
View File
@@ -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 -1
View File
@@ -1,7 +1,7 @@
{
"package": {
"productName": "nym-connect",
"version": "1.1.9"
"version": "1.1.8"
},
"build": {
"distDir": "../dist",
+74
View File
@@ -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,
},
]}
/>
);
};
+2 -20
View File
@@ -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>
</>
);
};
+14 -16
View File
@@ -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';
+132
View File
@@ -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;
}
}
+86 -53
View File
@@ -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,
],
);
-2
View File
@@ -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<{
-68
View File
@@ -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());
};
}, []);
};
+3 -2
View File
@@ -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');
+37 -42
View File
@@ -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}
+4 -4
View File
@@ -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>
))}
-85
View File
@@ -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>
);
};
+20 -21
View File
@@ -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>
);
};
+12 -13
View File
@@ -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>
);
};
+3 -4
View File
@@ -2,12 +2,12 @@ import React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { Box } from '@mui/material';
import { DateTime } from 'luxon';
import { 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 />
);
-1
View File
@@ -29,7 +29,6 @@ declare module '@mui/material/styles' {
*/
interface NymPalette {
highlight: string;
cta: string;
success: string;
warning: string;
info: string;
-1
View File
@@ -21,7 +21,6 @@ import {
const nymPalette: NymPalette = {
/** emphasises important elements */
highlight: '#21D072',
cta: '#FB6E4E',
success: '#21D073',
info: '#60D7EF',
warning: '#FFE600',
-4
View File
@@ -1,4 +0,0 @@
export interface UserDefinedGateway {
isActive: boolean;
gateway?: string;
}
BIN
View File
Binary file not shown.
-11
View File
@@ -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])
+3 -5
View File
@@ -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 -1
View File
@@ -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 = ""
+5 -12
View File
@@ -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>
+1 -1
View File
@@ -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>
+30 -28
View File
@@ -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>
);
+1
View File
@@ -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' });
}),
});
+6
View File
@@ -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>
);
+9
View File
@@ -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