Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d811446b6a | |||
| 65e252416c | |||
| fe59697aa9 | |||
| 566ceae325 | |||
| 6bc4d32573 | |||
| 1fc3c9f31c | |||
| 3cb08a410e | |||
| 1648aef1d3 | |||
| d98b6b107c | |||
| d3db0fc2cd | |||
| 06b823f4c1 | |||
| 1c0ce9a420 | |||
| dede8899cf | |||
| 70f6059ceb | |||
| fefd2dd267 | |||
| 0fbe77d934 | |||
| 8a267cfe3d | |||
| 5ebb2a3efe | |||
| de605dc3b9 | |||
| 6a01edf5fe | |||
| bfd86d1462 | |||
| 806b37bd83 | |||
| f4215d192f | |||
| 2524051da9 | |||
| 57245aaffe | |||
| 9398a055a2 | |||
| c8bf0fb853 | |||
| 91b9e2a1a7 | |||
| 426c9ae026 | |||
| 65e0b69a9d | |||
| ffff596d45 | |||
| ad826da782 | |||
| 3414eeea7a | |||
| 3a32d34fb4 | |||
| 248052aec7 | |||
| 90c00fc343 | |||
| 7bdfbfdcc5 | |||
| 44e22b74a5 | |||
| 7920c27648 | |||
| b9001458de | |||
| 3a8214cdb7 | |||
| f02f914ae2 |
+4
-4
@@ -19,10 +19,10 @@
|
||||
Cargo.* @durch @futurechimp @jstuczyn @neacsu @octol
|
||||
|
||||
# JS rules:
|
||||
*.js @mmsinclair @fmtabbara @Aid19801
|
||||
*.ts @mmsinclair @fmtabbara @Aid19801
|
||||
*.tsx @mmsinclair @fmtabbara @Aid19801
|
||||
*.jsx @mmsinclair @fmtabbara @Aid19801
|
||||
*.js @mmsinclair @fmtabbara
|
||||
*.ts @mmsinclair @fmtabbara
|
||||
*.tsx @mmsinclair @fmtabbara
|
||||
*.jsx @mmsinclair @fmtabbara
|
||||
|
||||
# Something looking like possible documentation rules:
|
||||
*.md @mfahampshire
|
||||
|
||||
@@ -9,6 +9,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
- socks5 client/websocket client: add `--force-register-gateway` flag, useful when rerunning init ([#1353])
|
||||
- nym-connect: initial proof-of-concept of a UI around the socks5 client was added
|
||||
- nym-connect: add ability to select network requester and gateway ([#1427]).
|
||||
- all: added network compilation target to `--help` (or `--version`) commands ([#1256]).
|
||||
- explorer-api: learned how to sum the delegations by owner in a new endpoint.
|
||||
- explorer-api: add apy values to `mix_nodes` endpoint
|
||||
@@ -63,6 +64,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#1388]: https://github.com/nymtech/nym/pull/1388
|
||||
[#1393]: https://github.com/nymtech/nym/pull/1393
|
||||
[#1404]: https://github.com/nymtech/nym/pull/1404
|
||||
[#1427]: https://github.com/nymtech/nym/pull/1427
|
||||
|
||||
## [nym-contracts-v1.0.1](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.1) (2022-06-22)
|
||||
|
||||
|
||||
+1
-1
@@ -81,4 +81,4 @@ default-members = [
|
||||
"explorer-api",
|
||||
]
|
||||
|
||||
exclude = ["explorer", "contracts", "tokenomics-py", "clients/webassembly", "nym-wallet"]
|
||||
exclude = ["explorer", "contracts", "tokenomics-py", "clients/webassembly", "nym-wallet", "nym-connect"]
|
||||
|
||||
@@ -9,8 +9,8 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
|
||||
|
||||
* nym-mixnode - shuffles [Sphinx](https://github.com/nymtech/sphinx) packets together to provide privacy against network-level attackers.
|
||||
* nym-client - an executable which you can build into your own applications. Use it for interacting with Nym nodes.
|
||||
* nym-socks5-client - a Socks5 proxy you can run on your machine, and use with existing applications
|
||||
* nym-gateway - acts sort of like a mailbox for mixnet messages, removing the need for directly delivery to potentially offline or firewalled devices.
|
||||
* nym-socks5-client - a Socks5 proxy you can run on your machine and use with existing applications.
|
||||
* nym-gateway - acts sort of like a mailbox for mixnet messages, which removes the need for direct delivery to potentially offline or firewalled devices.
|
||||
* nym-network-monitor - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing").
|
||||
* nym-explorer - a (projected) block explorer and (existing) mixnet viewer.
|
||||
* nym-wallet - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
|
||||
|
||||
+14
-14
@@ -221,7 +221,7 @@
|
||||
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
|
||||
integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78=
|
||||
integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==
|
||||
|
||||
"@protobufjs/base64@^1.1.2":
|
||||
version "1.1.2"
|
||||
@@ -236,12 +236,12 @@
|
||||
"@protobufjs/eventemitter@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
|
||||
integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A=
|
||||
integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==
|
||||
|
||||
"@protobufjs/fetch@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
|
||||
integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=
|
||||
integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==
|
||||
dependencies:
|
||||
"@protobufjs/aspromise" "^1.1.1"
|
||||
"@protobufjs/inquire" "^1.1.0"
|
||||
@@ -249,27 +249,27 @@
|
||||
"@protobufjs/float@^1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
|
||||
integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=
|
||||
integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==
|
||||
|
||||
"@protobufjs/inquire@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
|
||||
integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=
|
||||
integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==
|
||||
|
||||
"@protobufjs/path@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
|
||||
integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=
|
||||
integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==
|
||||
|
||||
"@protobufjs/pool@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
|
||||
integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=
|
||||
integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==
|
||||
|
||||
"@protobufjs/utf8@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
|
||||
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
|
||||
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
|
||||
|
||||
"@types/json-schema@^7.0.9":
|
||||
version "7.0.11"
|
||||
@@ -282,9 +282,9 @@
|
||||
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
||||
|
||||
"@types/long@^4.0.1":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
|
||||
integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
|
||||
integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==
|
||||
|
||||
"@types/node@>=13.7.0":
|
||||
version "17.0.23"
|
||||
@@ -1642,9 +1642,9 @@ protobufjs@^6.8.8, protobufjs@~6.11.2:
|
||||
long "^4.0.0"
|
||||
|
||||
protobufjs@~6.10.2:
|
||||
version "6.10.2"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.2.tgz#b9cb6bd8ec8f87514592ba3fdfd28e93f33a469b"
|
||||
integrity sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==
|
||||
version "6.10.3"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.3.tgz#11ed1dd02acbfcb330becf1611461d4b407f9eef"
|
||||
integrity sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==
|
||||
dependencies:
|
||||
"@protobufjs/aspromise" "^1.1.2"
|
||||
"@protobufjs/base64" "^1.1.2"
|
||||
|
||||
@@ -15,7 +15,7 @@ use std::ops::Neg;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(Clone, PartialEq))]
|
||||
#[cfg_attr(test, derive(Clone, PartialEq, Eq))]
|
||||
pub struct Ciphertexts {
|
||||
pub rr: [G1Projective; NUM_CHUNKS],
|
||||
pub ss: [G1Projective; NUM_CHUNKS],
|
||||
|
||||
@@ -345,7 +345,7 @@ impl PublicKey {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct PublicKeyWithProof {
|
||||
pub(crate) key: PublicKey,
|
||||
pub(crate) proof: ProofOfDiscreteLog,
|
||||
|
||||
@@ -68,7 +68,7 @@ impl<'a> Instance<'a> {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(Clone, PartialEq))]
|
||||
#[cfg_attr(test, derive(Clone, PartialEq, Eq))]
|
||||
pub struct ProofOfChunking {
|
||||
y0: G1Projective,
|
||||
bb: Vec<G1Projective>,
|
||||
|
||||
@@ -14,7 +14,7 @@ const DISCRETE_LOG_DOMAIN: &[u8] =
|
||||
b"NYM_COCONUT_NIDKG_V01_CS01_WITH_BLS12381_XMD:SHA-256_SSWU_RO_PROOF_DISCRETE_LOG";
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct ProofOfDiscreteLog {
|
||||
pub(crate) rand_commitment: G1Projective,
|
||||
pub(crate) response: Scalar,
|
||||
|
||||
@@ -77,7 +77,7 @@ impl<'a> Instance<'a> {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(Clone, PartialEq))]
|
||||
#[cfg_attr(test, derive(Clone, PartialEq, Eq))]
|
||||
pub struct ProofOfSecretSharing {
|
||||
ff: G1Projective,
|
||||
aa: G2Projective,
|
||||
|
||||
@@ -18,7 +18,7 @@ use std::collections::BTreeMap;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct Dealing {
|
||||
pub public_coefficients: PublicCoefficients,
|
||||
pub ciphertexts: Ciphertexts,
|
||||
|
||||
@@ -100,8 +100,10 @@ impl Network {
|
||||
pub fn statistics_service_url(&self) -> &str {
|
||||
match self {
|
||||
Network::MAINNET => crate::mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
|
||||
_ => {
|
||||
panic!("statistics service url is only available for mainnet!")
|
||||
Network::SANDBOX => crate::mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
|
||||
Network::QA => crate::mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
|
||||
Network::CUSTOM { .. } => {
|
||||
panic!("statistics service url is unavailable for a custom network")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
|
||||
hex_literal::hex!("0000000000000000000000000000000000000000");
|
||||
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq";
|
||||
|
||||
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "";
|
||||
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
|
||||
pub(crate) fn validators() -> Vec<ValidatorDetails> {
|
||||
vec![ValidatorDetails::new(
|
||||
"https://qa-validator.nymtech.net",
|
||||
|
||||
@@ -21,7 +21,7 @@ pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
|
||||
hex_literal::hex!("E8883BAeF3869e14E4823F46662e81D4F7d2A81F");
|
||||
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "nymt1jh0s6qu6tuw9ut438836mmn7f3f2wencrnmdj4";
|
||||
|
||||
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "";
|
||||
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
|
||||
pub(crate) fn validators() -> Vec<ValidatorDetails> {
|
||||
vec![ValidatorDetails::new(
|
||||
"https://sandbox-validator.nymtech.net",
|
||||
|
||||
@@ -20,7 +20,7 @@ pub type EphemeralKey = Scalar;
|
||||
|
||||
/// Two G1 points representing ElGamal ciphertext
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct Ciphertext(pub(crate) G1Projective, pub(crate) G1Projective);
|
||||
|
||||
impl TryFrom<&[u8]> for Ciphertext {
|
||||
@@ -73,7 +73,7 @@ impl Ciphertext {
|
||||
|
||||
/// PrivateKey used in the ElGamal encryption scheme to recover the plaintext
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct PrivateKey(pub(crate) Scalar);
|
||||
|
||||
impl PrivateKey {
|
||||
@@ -121,7 +121,7 @@ impl Base58 for PrivateKey {}
|
||||
// TODO: perhaps be more explicit and apart from gamma also store generator and group order?
|
||||
/// PublicKey used in the ElGamal encryption scheme to produce the ciphertext
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct PublicKey(G1Projective);
|
||||
|
||||
impl PublicKey {
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::Attribute;
|
||||
type ChallengeDigest = Sha256;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct ProofCmCs {
|
||||
challenge: Scalar,
|
||||
response_opening: Scalar,
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::utils::{hash_g1, try_deserialize_g1_projective};
|
||||
// TODO NAMING: double check this one
|
||||
// Lambda
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct BlindSignRequest {
|
||||
// cm
|
||||
commitment: G1Projective,
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::utils::{
|
||||
use crate::Base58;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct SecretKey {
|
||||
pub(crate) x: Scalar,
|
||||
pub(crate) ys: Vec<Scalar>,
|
||||
@@ -351,7 +351,7 @@ impl Bytable for VerificationKey {
|
||||
impl Base58 for VerificationKey {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct KeyPair {
|
||||
secret_key: SecretKey,
|
||||
verification_key: VerificationKey,
|
||||
|
||||
@@ -103,7 +103,7 @@ impl Bytable for Signature {
|
||||
impl Base58 for Signature {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct BlindedSignature(G1Projective, G1Projective);
|
||||
|
||||
impl Bytable for BlindedSignature {
|
||||
|
||||
+6
-6
@@ -1480,9 +1480,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/protobufjs": {
|
||||
"version": "6.10.2",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz",
|
||||
"integrity": "sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==",
|
||||
"version": "6.10.3",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.3.tgz",
|
||||
"integrity": "sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.2",
|
||||
@@ -3162,9 +3162,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"protobufjs": {
|
||||
"version": "6.10.2",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz",
|
||||
"integrity": "sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==",
|
||||
"version": "6.10.3",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.3.tgz",
|
||||
"integrity": "sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==",
|
||||
"requires": {
|
||||
"@protobufjs/aspromise": "^1.1.2",
|
||||
"@protobufjs/base64": "^1.1.2",
|
||||
|
||||
Generated
-2438
File diff suppressed because it is too large
Load Diff
Generated
+4
-12
@@ -590,7 +590,7 @@ version = "1.0.1"
|
||||
dependencies = [
|
||||
"config",
|
||||
"crypto",
|
||||
"dirs 3.0.2",
|
||||
"dirs",
|
||||
"futures",
|
||||
"gateway-client",
|
||||
"gateway-requests",
|
||||
@@ -1295,15 +1295,6 @@ dependencies = [
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "4.0.0"
|
||||
@@ -3386,7 +3377,7 @@ dependencies = [
|
||||
"bip39",
|
||||
"client-core",
|
||||
"config",
|
||||
"dirs 4.0.0",
|
||||
"dirs",
|
||||
"eyre",
|
||||
"fix-path-env",
|
||||
"futures",
|
||||
@@ -3394,6 +3385,7 @@ dependencies = [
|
||||
"nym-socks5-client",
|
||||
"pretty_env_logger",
|
||||
"rand 0.7.3",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
@@ -3418,7 +3410,7 @@ dependencies = [
|
||||
"config",
|
||||
"credential-storage",
|
||||
"crypto",
|
||||
"dirs 3.0.2",
|
||||
"dirs",
|
||||
"dotenv",
|
||||
"futures",
|
||||
"gateway-client",
|
||||
|
||||
@@ -30,11 +30,12 @@ serde_json = "1.0"
|
||||
tauri = { version = "=1.0.0-rc.8", features = ["ayatana-tray", "shell-open", "system-tray"] }
|
||||
tendermint-rpc = "0.23.0"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.19.1", features = ["sync"] }
|
||||
tokio = { version = "1.19.1", features = ["sync", "time"] }
|
||||
url = "2.2"
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.4.0"
|
||||
fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs", branch = "release"}
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
|
||||
client-core = { path = "../../clients/client-core" }
|
||||
config = { path = "../../common/config" }
|
||||
|
||||
@@ -2,31 +2,52 @@ use std::path::PathBuf;
|
||||
|
||||
use client_core::config::GatewayEndpoint;
|
||||
use log::info;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use client_core::config::Config as BaseConfig;
|
||||
use config::NymConfig;
|
||||
use nym_socks5::client::config::Config as Socks5Config;
|
||||
|
||||
pub static SOCKS5_CONFIG_ID: &str = "nym-connect";
|
||||
use crate::{error::BackendError, state::State};
|
||||
|
||||
// This is an open-proxy network-requester for testing
|
||||
// TODO: make this configurable from the UI
|
||||
// TODO: once we can set this is the UI, consider just removing it, and put in guards to halt if
|
||||
// user hasn't chosen the provider
|
||||
pub static PROVIDER_ADDRESS: &str = "8CrdmK4mYgZ5caMxGU4AvNeT1dXL8VSbgMYAjSFvnfut.2GLdZ1Jn9vkTBMf858evGNGDsPoeivUPw7zFNceLiLX3@BNjYZPxzcJwczXHHgBxCAyVJKxN6LPteDRrKapxWmexv";
|
||||
pub static SOCKS5_CONFIG_ID: &str = "nym-connect";
|
||||
|
||||
const DEFAULT_ETH_ENDPOINT: &str = "https://rinkeby.infura.io/v3/00000000000000000000000000000000";
|
||||
const DEFAULT_ETH_PRIVATE_KEY: &str =
|
||||
"0000000000000000000000000000000000000000000000000000000000000001";
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_config_file_location() -> String {
|
||||
let id: &str = SOCKS5_CONFIG_ID;
|
||||
Config::config_file_location(id)
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
pub fn append_config_id(gateway_id: &str) -> String {
|
||||
use std::fmt::Write as _;
|
||||
let mut id = SOCKS5_CONFIG_ID.to_string();
|
||||
write!(id, "-{}", gateway_id).expect("Failed to set config id");
|
||||
id
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_config_id(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<String, BackendError> {
|
||||
let guard = state.read().await;
|
||||
// TODO: return error instead
|
||||
let gateway_id = guard
|
||||
.get_gateway()
|
||||
.as_ref()
|
||||
.expect("The config id can not be determined before setting the gateway");
|
||||
Ok(append_config_id(gateway_id))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_config_file_location(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<String, BackendError> {
|
||||
let id = get_config_id(state).await?;
|
||||
Ok(Config::config_file_location(&id)
|
||||
.to_string_lossy()
|
||||
.to_string())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
socks5: Socks5Config,
|
||||
}
|
||||
@@ -55,12 +76,22 @@ impl Config {
|
||||
self.socks5.get_base_mut()
|
||||
}
|
||||
|
||||
pub async fn init(service_provider: Option<&String>, chosen_gateway_id: Option<&String>) {
|
||||
let service_provider = service_provider.map_or(PROVIDER_ADDRESS, String::as_str);
|
||||
let chosen_gateway_id = chosen_gateway_id.map(String::as_str);
|
||||
pub async fn init(service_provider: &str, chosen_gateway_id: &str) -> Result<(), BackendError> {
|
||||
info!("Initialising...");
|
||||
init_socks5(service_provider, chosen_gateway_id).await;
|
||||
|
||||
let service_provider = service_provider.to_owned();
|
||||
let chosen_gateway_id = chosen_gateway_id.to_owned();
|
||||
|
||||
// The client initialization was originally not written for this use case, so there are
|
||||
// lots of ways it can panic. Until we have proper error handling in the init code for the
|
||||
// clients we'll catch any panics here.
|
||||
std::panic::catch_unwind(move || {
|
||||
futures::executor::block_on(init_socks5(service_provider, chosen_gateway_id));
|
||||
})
|
||||
.map_err(|_| BackendError::InitializationPanic)?;
|
||||
|
||||
info!("Configuration saved 🚀");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn config_file_location(id: &str) -> PathBuf {
|
||||
@@ -68,16 +99,17 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn init_socks5(provider_address: &str, chosen_gateway_id: Option<&str>) {
|
||||
pub async fn init_socks5(provider_address: String, chosen_gateway_id: String) {
|
||||
log::info!("Initialising client...");
|
||||
|
||||
let id: &str = SOCKS5_CONFIG_ID;
|
||||
// Append the gateway id to the name id that we store the config under
|
||||
let id = append_config_id(&chosen_gateway_id);
|
||||
|
||||
log::debug!(
|
||||
"Attempting to use config file location: {}",
|
||||
Config::config_file_location(id).to_string_lossy(),
|
||||
Config::config_file_location(&id).to_string_lossy(),
|
||||
);
|
||||
let already_init = Config::config_file_location(id).exists();
|
||||
let already_init = Config::config_file_location(&id).exists();
|
||||
if already_init {
|
||||
log::info!(
|
||||
"SOCKS5 client \"{}\" was already initialised before! \
|
||||
@@ -92,7 +124,7 @@ pub async fn init_socks5(provider_address: &str, chosen_gateway_id: Option<&str>
|
||||
let register_gateway = !already_init || user_wants_force_register;
|
||||
|
||||
log::trace!("Creating config for id: {}", id);
|
||||
let mut config = Config::new(id, provider_address);
|
||||
let mut config = Config::new(id.as_str(), &provider_address);
|
||||
|
||||
// As far as I'm aware, these two are not used, they are only set because the socks5 init code
|
||||
// requires them for initialising the bandwidth controller.
|
||||
@@ -103,7 +135,13 @@ pub async fn init_socks5(provider_address: &str, chosen_gateway_id: Option<&str>
|
||||
.get_base_mut()
|
||||
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
|
||||
|
||||
let gateway = setup_gateway(id, register_gateway, chosen_gateway_id, config.get_socks5()).await;
|
||||
let gateway = setup_gateway(
|
||||
&id,
|
||||
register_gateway,
|
||||
Some(&chosen_gateway_id),
|
||||
config.get_socks5(),
|
||||
)
|
||||
.await;
|
||||
config.get_base_mut().with_gateway_endpoint(gateway);
|
||||
|
||||
let config_save_location = config.get_socks5().get_config_file_save_location();
|
||||
|
||||
@@ -14,6 +14,13 @@ pub enum BackendError {
|
||||
NoServiceProviderSet,
|
||||
#[error("No gateway provider set")]
|
||||
NoGatewaySet,
|
||||
#[error("{source}")]
|
||||
ReqwestError {
|
||||
#[from]
|
||||
source: reqwest::Error,
|
||||
},
|
||||
#[error("Initialization failed with a panic")]
|
||||
InitializationPanic,
|
||||
}
|
||||
|
||||
impl Serialize for BackendError {
|
||||
|
||||
@@ -19,6 +19,7 @@ mod menu;
|
||||
mod models;
|
||||
mod operations;
|
||||
mod state;
|
||||
mod tasks;
|
||||
mod window;
|
||||
|
||||
fn main() {
|
||||
@@ -35,6 +36,7 @@ fn main() {
|
||||
.manage(Arc::new(RwLock::new(State::new())))
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
crate::config::get_config_file_location,
|
||||
crate::config::get_config_id,
|
||||
crate::operations::connection::connect::get_gateway,
|
||||
crate::operations::connection::connect::get_service_provider,
|
||||
crate::operations::connection::connect::set_gateway,
|
||||
@@ -42,6 +44,7 @@ fn main() {
|
||||
crate::operations::connection::connect::start_connecting,
|
||||
crate::operations::connection::disconnect::start_disconnecting,
|
||||
crate::operations::window::hide_window,
|
||||
crate::operations::directory::get_services,
|
||||
])
|
||||
.menu(Menu::new().add_default_app_submenu_if_macos())
|
||||
.system_tray(create_tray_menu())
|
||||
@@ -61,5 +64,10 @@ fn setup_logging() {
|
||||
|
||||
log_builder
|
||||
.filter_module("handlebars", log::LevelFilter::Warn)
|
||||
.filter_module("mio", log::LevelFilter::Warn)
|
||||
.filter_module("sled", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("want", log::LevelFilter::Warn)
|
||||
.init();
|
||||
}
|
||||
|
||||
@@ -30,3 +30,23 @@ pub const APP_EVENT_CONNECTION_STATUS_CHANGED: &str = "app:connection-status-cha
|
||||
pub struct AppEventConnectionStatusChangedPayload {
|
||||
pub status: ConnectionStatusKind,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DirectoryService {
|
||||
pub id: String,
|
||||
pub description: String,
|
||||
pub items: Vec<DirectoryServiceProvider>,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DirectoryServiceProvider {
|
||||
pub id: String,
|
||||
pub description: String,
|
||||
/// Address of the network requester in the form "<gateway_id>.<service_provider_id>"
|
||||
/// e.g. DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx@2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh
|
||||
pub address: String,
|
||||
/// Address of the gateway, e.g. 2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh
|
||||
pub gateway: String,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use crate::error::BackendError;
|
||||
use crate::models::ConnectResult;
|
||||
use crate::State;
|
||||
use crate::{error::BackendError, models::ConnectResult, tasks::start_disconnect_listener, State};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
@@ -9,9 +7,18 @@ pub async fn start_connecting(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
window: tauri::Window<tauri::Wry>,
|
||||
) -> Result<ConnectResult, BackendError> {
|
||||
let mut guard = state.write().await;
|
||||
let status_receiver = {
|
||||
let mut guard = state.write().await;
|
||||
|
||||
guard.start_connecting(&window).await;
|
||||
log::trace!("Start connecting with:");
|
||||
log::trace!(" service_provider: {:?}", guard.get_service_provider());
|
||||
log::trace!(" gateway: {:?}", guard.get_gateway());
|
||||
guard.start_connecting(&window).await?
|
||||
};
|
||||
|
||||
// Setup task for checking status
|
||||
let state = state.inner().clone();
|
||||
start_disconnect_listener(state, window, status_receiver);
|
||||
|
||||
Ok(ConnectResult {
|
||||
// WIP(JON): fixme
|
||||
@@ -35,6 +42,7 @@ pub async fn set_service_provider(
|
||||
service_provider: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
log::trace!("Setting service_provider: {service_provider}");
|
||||
let mut guard = state.write().await;
|
||||
guard.set_service_provider(service_provider);
|
||||
Ok(())
|
||||
@@ -56,6 +64,7 @@ pub async fn set_gateway(
|
||||
gateway: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
log::trace!("Setting gateway: {gateway}");
|
||||
let mut guard = state.write().await;
|
||||
guard.set_gateway(gateway);
|
||||
Ok(())
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
use crate::error::BackendError;
|
||||
use crate::models::DirectoryService;
|
||||
|
||||
static SERVICE_PROVIDER_WELLKNOWN_URL: &str =
|
||||
"https://nymtech.net/.wellknown/connect/service-providers.json";
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_services() -> Result<Vec<DirectoryService>, BackendError> {
|
||||
let res = reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL)
|
||||
.await?
|
||||
.json::<Vec<DirectoryService>>()
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod connection;
|
||||
pub mod directory;
|
||||
pub mod window;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
use client_core::config::GatewayEndpoint;
|
||||
use futures::channel::mpsc;
|
||||
use futures::SinkExt;
|
||||
use log::info;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::SinkExt;
|
||||
use tauri::Manager;
|
||||
|
||||
use config::NymConfig;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use nym_socks5::client::NymClient as Socks5NymClient;
|
||||
use nym_socks5::client::{Socks5ControlMessage, Socks5ControlMessageSender};
|
||||
|
||||
use crate::config::SOCKS5_CONFIG_ID;
|
||||
use crate::models::{
|
||||
AppEventConnectionStatusChangedPayload, ConnectionStatusKind,
|
||||
APP_EVENT_CONNECTION_STATUS_CHANGED,
|
||||
use crate::{
|
||||
config::append_config_id,
|
||||
error::BackendError,
|
||||
models::{
|
||||
AppEventConnectionStatusChangedPayload, ConnectionStatusKind,
|
||||
APP_EVENT_CONNECTION_STATUS_CHANGED,
|
||||
},
|
||||
tasks::{start_nym_socks5_client, StatusReceiver},
|
||||
};
|
||||
use tauri::Manager;
|
||||
|
||||
pub struct State {
|
||||
status: ConnectionStatusKind,
|
||||
@@ -63,29 +63,56 @@ impl State {
|
||||
self.gateway = Some(gateway);
|
||||
}
|
||||
|
||||
pub async fn init_config(&self) {
|
||||
crate::config::Config::init(self.service_provider.as_ref(), self.gateway.as_ref()).await;
|
||||
pub async fn init_config(&self) -> Result<(), BackendError> {
|
||||
let service_provider = self
|
||||
.service_provider
|
||||
.as_ref()
|
||||
.expect("Attempting to init without service provider");
|
||||
let gateway = self
|
||||
.gateway
|
||||
.as_ref()
|
||||
.expect("Attempting to init without gateway");
|
||||
crate::config::Config::init(service_provider, gateway).await
|
||||
}
|
||||
|
||||
pub async fn start_connecting(&mut self, window: &tauri::Window<tauri::Wry>) {
|
||||
info!("Connecting");
|
||||
pub async fn start_connecting(
|
||||
&mut self,
|
||||
window: &tauri::Window<tauri::Wry>,
|
||||
) -> Result<StatusReceiver, BackendError> {
|
||||
log::info!("Connecting");
|
||||
self.set_state(ConnectionStatusKind::Connecting, window);
|
||||
self.status = ConnectionStatusKind::Connecting;
|
||||
|
||||
// Setup configuration by writing to file
|
||||
self.init_config().await;
|
||||
if let Err(err) = self.init_config().await {
|
||||
log::warn!("Failed to initialize: {}", err);
|
||||
|
||||
// Kick of the main task and get the channel for controlling it
|
||||
let (sender, used_gateway) = start_nym_socks5_client();
|
||||
// Wait a little to give the user some rudimentary feedback that the click actually
|
||||
// registered.
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
self.set_state(ConnectionStatusKind::Disconnected, window);
|
||||
self.status = ConnectionStatusKind::Disconnected;
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
// Kick off the main task and get the channel for controlling it
|
||||
let id = append_config_id(
|
||||
self.gateway
|
||||
.as_ref()
|
||||
.expect("Attempting to start without gateway"),
|
||||
);
|
||||
let (sender, used_gateway, status_receiver) = start_nym_socks5_client(&id);
|
||||
self.gateway = Some(used_gateway.gateway_id);
|
||||
self.socks5_client_sender = Some(sender);
|
||||
|
||||
self.status = ConnectionStatusKind::Connected;
|
||||
self.set_state(ConnectionStatusKind::Connected, window);
|
||||
|
||||
Ok(status_receiver)
|
||||
}
|
||||
|
||||
pub async fn start_disconnecting(&mut self, window: &tauri::Window<tauri::Wry>) {
|
||||
info!("Disconnecting");
|
||||
log::info!("Disconnecting");
|
||||
self.set_state(ConnectionStatusKind::Disconnecting, window);
|
||||
self.status = ConnectionStatusKind::Disconnecting;
|
||||
|
||||
@@ -93,32 +120,11 @@ impl State {
|
||||
if let Some(ref mut sender) = self.socks5_client_sender {
|
||||
sender.send(Socks5ControlMessage::Stop).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn mark_disconnected(&mut self, window: &tauri::Window<tauri::Wry>) {
|
||||
log::info!("Disconnected");
|
||||
self.status = ConnectionStatusKind::Disconnected;
|
||||
self.set_state(ConnectionStatusKind::Disconnected, window);
|
||||
}
|
||||
}
|
||||
|
||||
fn start_nym_socks5_client() -> (Socks5ControlMessageSender, GatewayEndpoint) {
|
||||
let id: &str = SOCKS5_CONFIG_ID;
|
||||
|
||||
info!("Loading config from file");
|
||||
let config = nym_socks5::client::config::Config::load_from_file(Some(id)).unwrap();
|
||||
let used_gateway = config.get_base().get_gateway_endpoint().clone();
|
||||
|
||||
let mut socks5_client = Socks5NymClient::new(config);
|
||||
info!("Starting socks5 client");
|
||||
|
||||
let (sender, receiver) = mpsc::unbounded();
|
||||
|
||||
// Spawn a separate runtime for the socks5 client so we can forcefully terminate.
|
||||
// Once we can gracefully shutdown the socks5 client we can get rid of this.
|
||||
std::thread::spawn(|| {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
rt.block_on(async move {
|
||||
socks5_client.run_and_listen(receiver).await;
|
||||
});
|
||||
});
|
||||
|
||||
(sender, used_gateway)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
use client_core::config::GatewayEndpoint;
|
||||
use futures::channel::mpsc;
|
||||
use log::info;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use config::NymConfig;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use nym_socks5::client::NymClient as Socks5NymClient;
|
||||
use nym_socks5::client::Socks5ControlMessageSender;
|
||||
|
||||
use crate::state::State;
|
||||
|
||||
pub type StatusReceiver = futures::channel::oneshot::Receiver<Socks5StatusMessage>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Socks5StatusMessage {
|
||||
/// The SOCKS5 task successfully stopped
|
||||
Stopped,
|
||||
}
|
||||
|
||||
pub fn start_nym_socks5_client(
|
||||
id: &str,
|
||||
) -> (Socks5ControlMessageSender, GatewayEndpoint, StatusReceiver) {
|
||||
info!("Loading config from file: {id}");
|
||||
// TODO: handle this gracefully!
|
||||
let config = nym_socks5::client::config::Config::load_from_file(Some(id)).unwrap();
|
||||
let used_gateway = config.get_base().get_gateway_endpoint().clone();
|
||||
|
||||
let mut socks5_client = Socks5NymClient::new(config);
|
||||
info!("Starting socks5 client");
|
||||
|
||||
// Channel to send control messages to the socks5 client
|
||||
let (socks5_ctrl_tx, socks5_ctrl_rx) = mpsc::unbounded();
|
||||
|
||||
// Channel to signal back to the main task when the socks5 client finishes, and why
|
||||
let (socks5_status_tx, socks5_status_rx) = futures::channel::oneshot::channel();
|
||||
|
||||
// Spawn a separate runtime for the socks5 client so we can forcefully terminate.
|
||||
// Once we can gracefully shutdown the socks5 client we can get rid of this.
|
||||
std::thread::spawn(|| {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
rt.block_on(async move {
|
||||
socks5_client.run_and_listen(socks5_ctrl_rx).await;
|
||||
});
|
||||
|
||||
log::info!("SOCKS5 task finished");
|
||||
socks5_status_tx.send(Socks5StatusMessage::Stopped).unwrap();
|
||||
});
|
||||
|
||||
(socks5_ctrl_tx, used_gateway, socks5_status_rx)
|
||||
}
|
||||
|
||||
pub fn start_disconnect_listener(
|
||||
state: Arc<RwLock<State>>,
|
||||
window: tauri::Window<tauri::Wry>,
|
||||
status_receiver: StatusReceiver,
|
||||
) {
|
||||
log::trace!("Starting disconnect listener");
|
||||
tokio::spawn(async move {
|
||||
match status_receiver.await {
|
||||
Ok(Socks5StatusMessage::Stopped) => {
|
||||
log::info!("SOCKS5 task reported it has finished");
|
||||
}
|
||||
Err(_) => {
|
||||
log::info!("SOCKS5 task appears to have stopped abruptly");
|
||||
}
|
||||
}
|
||||
|
||||
let mut state_w = state.write().await;
|
||||
state_w.mark_disconnected(&window).await;
|
||||
});
|
||||
}
|
||||
+11
-1
@@ -7,6 +7,7 @@ import { ConnectedLayout } from './layouts/ConnectedLayout';
|
||||
export const App: React.FC = () => {
|
||||
const context = useClientContext();
|
||||
const [busy, setBusy] = React.useState<boolean>();
|
||||
|
||||
const handleConnectClick = React.useCallback(async () => {
|
||||
const oldStatus = context.connectionStatus;
|
||||
if (oldStatus === ConnectionStatusKind.connected || oldStatus === ConnectionStatusKind.disconnected) {
|
||||
@@ -29,7 +30,15 @@ export const App: React.FC = () => {
|
||||
context.connectionStatus === ConnectionStatusKind.disconnected ||
|
||||
context.connectionStatus === ConnectionStatusKind.connecting
|
||||
) {
|
||||
return <DefaultLayout status={context.connectionStatus} busy={busy} onConnectClick={handleConnectClick} />;
|
||||
return (
|
||||
<DefaultLayout
|
||||
status={context.connectionStatus}
|
||||
busy={busy}
|
||||
onConnectClick={handleConnectClick}
|
||||
services={context.services}
|
||||
onServiceProviderChange={context.setServiceProvider}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -40,6 +49,7 @@ export const App: React.FC = () => {
|
||||
ipAddress="127.0.0.1"
|
||||
port={1080}
|
||||
connectedSince={context.connectedSince}
|
||||
serviceProvider={context.serviceProvider}
|
||||
stats={[
|
||||
{
|
||||
label: 'in:',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
|
||||
export const AppWindowFrame: React.FC = ({ children }) => (
|
||||
<Box
|
||||
@@ -14,11 +13,10 @@ export const AppWindowFrame: React.FC = ({ children }) => (
|
||||
}}
|
||||
>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<svg width="22" height="6" viewBox="0 0 22 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M6.86777 6H5.35495L1.22609 1.32517V6H0V0H1.54986L5.67872 4.67354V0H6.86777V6ZM20.4496 0L18.5658 2.13277L16.6821 0H15.1322V6H16.3578V1.32517L18.2959 3.52046C18.4457 3.68998 18.6865 3.68998 18.8363 3.52046L20.7745 1.32517V6H22V0H20.4496ZM10.4063 3.13181V6H11.6318V3.13181L14.4527 0H12.9028L11.018 2.13277L9.13421 0H7.58435L10.4063 3.13181Z"
|
||||
fill="#F2F2F2"
|
||||
/>
|
||||
<svg width="22" height="6" viewBox="0 0 210 56" fill="#F2F2F2" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M45.8829 0.142822H45.7169V0.28114V48.637L25.3289 0.225818L25.3012 0.142822H25.1905H13.6272H0.652966H0.514648V0.28114V55.7189V55.8572H0.652966H13.6272H13.7655V55.7189V7.28002L34.2365 55.7742L34.2642 55.8572H34.3748H45.8829H58.8294H58.9677V55.7189V0.28114V0.142822H58.8294H45.8829Z" />
|
||||
<path d="M209.347 0.142822H184.616H184.477L184.45 0.253483L171.78 48.8583L159.082 0.253483L159.054 0.142822H158.944H134.157H133.991V0.28114V55.7189V55.8572H134.157H147.104H147.242V55.7189V7.66731L159.774 55.7466L159.801 55.8572H159.94H183.564H183.675L183.703 55.7466L196.234 7.66731V55.7189V55.8572H196.373H209.347H209.485V55.7189V0.28114V0.142822H209.347Z" />
|
||||
<path d="M112.663 0.142822H112.58L112.552 0.198153L96.8116 27.5574L80.988 0.198153L80.9604 0.142822H80.8774H65.9114H65.6348L65.7731 0.364136L90.1447 42.5787V55.7189V55.8572H90.283H103.257H103.396V55.7189V42.5787L127.767 0.364136L127.905 0.142822H127.629H112.663Z" />
|
||||
</svg>
|
||||
</Box>
|
||||
{children}
|
||||
|
||||
@@ -3,17 +3,21 @@ import { ConnectionStatusKind } from '../types';
|
||||
|
||||
export const ConnectionButton: React.FC<{
|
||||
status: ConnectionStatusKind;
|
||||
disabled?: boolean;
|
||||
busy?: boolean;
|
||||
isError?: boolean;
|
||||
onClick?: (status: ConnectionStatusKind) => void;
|
||||
}> = ({ status, isError, onClick, busy }) => {
|
||||
}> = ({ status, disabled, isError, onClick, busy }) => {
|
||||
const [hover, setHover] = React.useState<boolean>(false);
|
||||
|
||||
const handleClick = React.useCallback(() => {
|
||||
if (disabled === true) {
|
||||
return;
|
||||
}
|
||||
if (onClick) {
|
||||
onClick(status);
|
||||
}
|
||||
}, [status]);
|
||||
}, [status, disabled]);
|
||||
|
||||
const statusText = getStatusText(status, hover);
|
||||
const statusTextColor = isError ? '#40475C' : '#FFF';
|
||||
@@ -21,16 +25,17 @@ export const ConnectionButton: React.FC<{
|
||||
|
||||
return (
|
||||
<svg
|
||||
opacity={disabled ? 0.75 : 1}
|
||||
width="208"
|
||||
height="208"
|
||||
viewBox="0 0 208 208"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
onMouseEnter={() => setHover(true)}
|
||||
onMouseLeave={() => setHover(false)}
|
||||
onMouseEnter={() => !disabled && setHover(true)}
|
||||
onMouseLeave={() => !disabled && setHover(false)}
|
||||
>
|
||||
<g transform="translate(-46 -46)">
|
||||
<g onClick={handleClick} style={{ cursor: 'pointer' }}>
|
||||
<g onClick={handleClick} style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}>
|
||||
<g filter="url(#filter0_f_2_303)">
|
||||
<circle cx="150" cy="150" r="70" fill="#3B445F" />
|
||||
</g>
|
||||
|
||||
@@ -4,6 +4,7 @@ import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
||||
import CircleOutlinedIcon from '@mui/icons-material/CircleOutlined';
|
||||
import { DateTime } from 'luxon';
|
||||
import { ConnectionStatusKind } from '../types';
|
||||
import { ServiceProvider } from '../types/directory';
|
||||
|
||||
const FONT_SIZE = '16px';
|
||||
const FONT_WEIGHT = '600';
|
||||
@@ -57,7 +58,8 @@ const ConnectionStatusContent: React.FC<{
|
||||
export const ConnectionStatus: React.FC<{
|
||||
status: ConnectionStatusKind;
|
||||
connectedSince?: DateTime;
|
||||
}> = ({ status, connectedSince }) => {
|
||||
serviceProvider?: ServiceProvider;
|
||||
}> = ({ status, connectedSince, serviceProvider }) => {
|
||||
const color =
|
||||
status === ConnectionStatusKind.connected || status === ConnectionStatusKind.disconnecting ? '#21D072' : '#888';
|
||||
const [duration, setDuration] = React.useState<string>();
|
||||
@@ -72,13 +74,16 @@ export const ConnectionStatus: React.FC<{
|
||||
};
|
||||
}, [status, connectedSince]);
|
||||
return (
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Box color={color} fontSize={FONT_SIZE} display="flex" alignItems="center">
|
||||
<ConnectionStatusContent status={status} />
|
||||
<>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Box color={color} fontSize={FONT_SIZE} display="flex" alignItems="center">
|
||||
<ConnectionStatusContent status={status} />
|
||||
</Box>
|
||||
<Typography color={color} fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE}>
|
||||
{status === ConnectionStatusKind.connected && duration}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography color={color} fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE}>
|
||||
{status === ConnectionStatusKind.connected && duration}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>{serviceProvider && <Typography fontSize={12}>{serviceProvider.description}</Typography>}</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import React from 'react';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import ArrowDropDownCircleIcon from '@mui/icons-material/ArrowDropDownCircle';
|
||||
import { Box, CircularProgress, Stack, Tooltip, Typography } from '@mui/material';
|
||||
import { ServiceProvider, Services } from '../types/directory';
|
||||
|
||||
export const ServiceProviderSelector: React.FC<{
|
||||
onChange?: (serviceProvider: ServiceProvider) => void;
|
||||
services?: Services;
|
||||
}> = ({ services, onChange }) => {
|
||||
const [serviceProvider, setServiceProvider] = React.useState<ServiceProvider | undefined>();
|
||||
const textEl = React.useRef<null | HTMLElement>(null);
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const handleClick = () => {
|
||||
setAnchorEl(textEl.current);
|
||||
};
|
||||
const handleClose = (newServiceProvider?: ServiceProvider) => {
|
||||
if (newServiceProvider) {
|
||||
setServiceProvider(newServiceProvider);
|
||||
onChange?.(newServiceProvider);
|
||||
}
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
if (!services) {
|
||||
return (
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between" sx={{ mt: 3 }}>
|
||||
<Typography fontSize={14} fontWeight={700} color={(theme) => theme.palette.common.white}>
|
||||
<CircularProgress size={14} sx={{ mr: 1 }} color="inherit" />
|
||||
Loading services...
|
||||
</Typography>
|
||||
<IconButton id="service-provider-button" disabled>
|
||||
<ArrowDropDownCircleIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between" sx={{ mt: 3 }}>
|
||||
<Typography
|
||||
ref={textEl}
|
||||
fontSize={14}
|
||||
fontWeight={700}
|
||||
color={(theme) => (serviceProvider ? undefined : theme.palette.primary.main)}
|
||||
>
|
||||
{serviceProvider ? serviceProvider.description : 'Select a service'}
|
||||
</Typography>
|
||||
<IconButton
|
||||
id="service-provider-button"
|
||||
aria-controls={open ? 'basic-menu' : undefined}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<ArrowDropDownCircleIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Menu
|
||||
id="service-provider-menu"
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={() => handleClose()}
|
||||
MenuListProps={{
|
||||
'aria-labelledby': 'service-provider-button',
|
||||
}}
|
||||
>
|
||||
{services.map((service) => (
|
||||
<>
|
||||
<MenuItem disabled dense sx={{ fontSize: 'small', fontWeight: 'bold', mb: -1 }}>
|
||||
{service.description}
|
||||
</MenuItem>
|
||||
{service.items.map((sp) => (
|
||||
<MenuItem dense sx={{ fontSize: 'small', ml: 2, height: 'auto' }} onClick={() => handleClose(sp)}>
|
||||
<Tooltip
|
||||
title={
|
||||
<Stack direction="column">
|
||||
<Typography fontSize="inherit">
|
||||
<code>{sp.id}</code>
|
||||
</Typography>
|
||||
<Typography fontSize="inherit" fontWeight={700}>
|
||||
{sp.description}
|
||||
</Typography>
|
||||
<Typography fontSize="inherit">
|
||||
Gateway <code>{sp.gateway.slice(0, 10)}...</code>
|
||||
</Typography>
|
||||
<Typography fontSize="inherit">
|
||||
Provider <code>{sp.address.slice(0, 10)}...</code>
|
||||
</Typography>
|
||||
</Stack>
|
||||
}
|
||||
arrow
|
||||
placement="top"
|
||||
>
|
||||
<Typography fontSize="inherit" noWrap>
|
||||
{sp.description}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</MenuItem>
|
||||
))}
|
||||
</>
|
||||
))}
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { listen, UnlistenFn } from '@tauri-apps/api/event';
|
||||
import { ConnectionStatusKind } from '../types';
|
||||
import { ConnectionStatsItem } from '../components/ConnectionStats';
|
||||
import { ServiceProvider, Services } from '../types/directory';
|
||||
|
||||
const TAURI_EVENT_STATUS_CHANGED = 'app:connection-status-changed';
|
||||
|
||||
@@ -14,11 +15,14 @@ type TClientContext = {
|
||||
connectionStatus: ConnectionStatusKind;
|
||||
connectionStats?: ConnectionStatsItem[];
|
||||
connectedSince?: DateTime;
|
||||
services?: Services;
|
||||
serviceProvider?: ServiceProvider;
|
||||
|
||||
setMode: (mode: ModeType) => void;
|
||||
setConnectionStatus: (connectionStatus: ConnectionStatusKind) => void;
|
||||
setConnectionStats: (connectionStats: ConnectionStatsItem[] | undefined) => void;
|
||||
setConnectedSince: (connectedSince: DateTime | undefined) => void;
|
||||
setServiceProvider: (serviceProvider: ServiceProvider) => void;
|
||||
|
||||
startConnecting: () => Promise<void>;
|
||||
startDisconnecting: () => Promise<void>;
|
||||
@@ -31,6 +35,14 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
const [connectionStatus, setConnectionStatus] = useState<ConnectionStatusKind>(ConnectionStatusKind.disconnected);
|
||||
const [connectionStats, setConnectionStats] = useState<ConnectionStatsItem[]>();
|
||||
const [connectedSince, setConnectedSince] = useState<DateTime>();
|
||||
const [services, setServices] = React.useState<Services>();
|
||||
const [serviceProvider, setRawServiceProvider] = React.useState<ServiceProvider>();
|
||||
|
||||
useEffect(() => {
|
||||
invoke('get_services').then((result) => {
|
||||
setServices(result as Services);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let unlisten: UnlistenFn | undefined;
|
||||
@@ -59,6 +71,12 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
await invoke('start_disconnecting');
|
||||
}, []);
|
||||
|
||||
const setServiceProvider = useCallback(async (newServiceProvider: ServiceProvider) => {
|
||||
await invoke('set_gateway', { gateway: newServiceProvider.gateway });
|
||||
await invoke('set_service_provider', { serviceProvider: newServiceProvider.address });
|
||||
setRawServiceProvider(newServiceProvider);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ClientContext.Provider
|
||||
value={{
|
||||
@@ -72,6 +90,9 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
setConnectedSince,
|
||||
startConnecting,
|
||||
startDisconnecting,
|
||||
services,
|
||||
serviceProvider,
|
||||
setServiceProvider,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ConnectionStats, ConnectionStatsItem } from '../components/ConnectionSt
|
||||
import { NeedHelp } from '../components/NeedHelp';
|
||||
import { ConnectionButton } from '../components/ConnectionButton';
|
||||
import { IpAddressAndPort } from '../components/IpAddressAndPort';
|
||||
import { ServiceProvider } from '../types/directory';
|
||||
|
||||
export const ConnectedLayout: React.FC<{
|
||||
status: ConnectionStatusKind;
|
||||
@@ -18,15 +19,16 @@ export const ConnectedLayout: React.FC<{
|
||||
busy?: boolean;
|
||||
isError?: boolean;
|
||||
onConnectClick?: (status: ConnectionStatusKind) => void;
|
||||
}> = ({ status, stats, ipAddress, port, connectedSince, busy, isError, onConnectClick }) => (
|
||||
serviceProvider?: ServiceProvider;
|
||||
}> = ({ status, stats, ipAddress, port, connectedSince, busy, isError, serviceProvider, onConnectClick }) => (
|
||||
<AppWindowFrame>
|
||||
<Box pb={4}>
|
||||
<ConnectionStatus status={status} connectedSince={connectedSince} />
|
||||
<ConnectionStatus status={status} connectedSince={connectedSince} serviceProvider={serviceProvider} />
|
||||
</Box>
|
||||
<Box pb={4}>
|
||||
<IpAddressAndPort label="SOCKS5 Proxy" ipAddress={ipAddress} port={port} />
|
||||
</Box>
|
||||
<ConnectionStats stats={stats} />
|
||||
{/* <ConnectionStats stats={stats} /> */}
|
||||
<ConnectionButton status={status} busy={busy} onClick={onConnectClick} isError={isError} />
|
||||
<NeedHelp />
|
||||
</AppWindowFrame>
|
||||
|
||||
@@ -4,21 +4,39 @@ import { AppWindowFrame } from '../components/AppWindowFrame';
|
||||
import { ConnectionButton } from '../components/ConnectionButton';
|
||||
import { ConnectionStatusKind } from '../types';
|
||||
import { NeedHelp } from '../components/NeedHelp';
|
||||
import { ServiceProviderSelector } from '../components/ServiceProviderSelector';
|
||||
import { ServiceProvider, Services } from '../types/directory';
|
||||
|
||||
export const DefaultLayout: React.FC<{
|
||||
status: ConnectionStatusKind;
|
||||
services?: Services;
|
||||
busy?: boolean;
|
||||
isError?: boolean;
|
||||
onConnectClick?: (status: ConnectionStatusKind) => void;
|
||||
}> = ({ status, busy, isError, onConnectClick }) => (
|
||||
<AppWindowFrame>
|
||||
<Typography fontWeight="700" fontSize="14px" textAlign="center">
|
||||
Connect, your privacy will be 100% protected thanks to the Nym Mixnet
|
||||
</Typography>
|
||||
<Typography fontWeight="700" fontSize="14px" textAlign="center" color="#60D6EF" pt={2}>
|
||||
You are not protected now
|
||||
</Typography>
|
||||
<ConnectionButton status={status} busy={busy} isError={isError} onClick={onConnectClick} />
|
||||
<NeedHelp />
|
||||
</AppWindowFrame>
|
||||
);
|
||||
onServiceProviderChange?: (serviceProvider: ServiceProvider) => void;
|
||||
}> = ({ status, services, busy, isError, onConnectClick, onServiceProviderChange }) => {
|
||||
const [serviceProvider, setServiceProvider] = React.useState<ServiceProvider | undefined>();
|
||||
const handleServiceProviderChange = (newServiceProvider: ServiceProvider) => {
|
||||
setServiceProvider(newServiceProvider);
|
||||
onServiceProviderChange?.(newServiceProvider);
|
||||
};
|
||||
return (
|
||||
<AppWindowFrame>
|
||||
<Typography fontWeight="700" fontSize="14px" textAlign="center">
|
||||
Connect, your privacy will be 100% protected thanks to the Nym Mixnet
|
||||
</Typography>
|
||||
<Typography fontWeight="700" fontSize="14px" textAlign="center" color="#60D6EF" pt={2}>
|
||||
You are not protected now
|
||||
</Typography>
|
||||
<ServiceProviderSelector services={services} onChange={handleServiceProviderChange} />
|
||||
<ConnectionButton
|
||||
status={status}
|
||||
disabled={serviceProvider === undefined}
|
||||
busy={busy}
|
||||
isError={isError}
|
||||
onClick={onConnectClick}
|
||||
/>
|
||||
<NeedHelp />
|
||||
</AppWindowFrame>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useClientContext } from '../context/main';
|
||||
import { ConnectionStatusKind } from '../types';
|
||||
import { DefaultLayout } from '../layouts/DefaultLayout';
|
||||
import { ConnectedLayout } from '../layouts/ConnectedLayout';
|
||||
import { Services } from '../types/directory';
|
||||
|
||||
export default {
|
||||
title: 'App/Flow',
|
||||
@@ -16,6 +17,19 @@ export default {
|
||||
export const Mock: ComponentStory<typeof AppWindowFrame> = () => {
|
||||
const context = useClientContext();
|
||||
const [busy, setBusy] = React.useState<boolean>();
|
||||
const services: Services = [
|
||||
{
|
||||
id: 'keybase',
|
||||
description: 'Keybase',
|
||||
items: [
|
||||
{
|
||||
id: 'nym-keybase',
|
||||
description: 'Nym Keybase Service Provider',
|
||||
address: '1234.5678',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const handleConnectClick = React.useCallback(() => {
|
||||
const oldStatus = context.connectionStatus;
|
||||
if (oldStatus === ConnectionStatusKind.connected || oldStatus === ConnectionStatusKind.disconnected) {
|
||||
@@ -53,7 +67,12 @@ export const Mock: ComponentStory<typeof AppWindowFrame> = () => {
|
||||
) {
|
||||
return (
|
||||
<Box p={4} sx={{ background: 'white' }}>
|
||||
<DefaultLayout status={context.connectionStatus} busy={busy} onConnectClick={handleConnectClick} />
|
||||
<DefaultLayout
|
||||
status={context.connectionStatus}
|
||||
busy={busy}
|
||||
onConnectClick={handleConnectClick}
|
||||
services={services}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -67,6 +86,7 @@ export const Mock: ComponentStory<typeof AppWindowFrame> = () => {
|
||||
ipAddress="127.0.0.1"
|
||||
port={1080}
|
||||
connectedSince={context.connectedSince}
|
||||
serviceProvider={services[0].items[0]}
|
||||
stats={[
|
||||
{
|
||||
label: 'in:',
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
export interface ServiceProvider {
|
||||
id: string;
|
||||
description: string;
|
||||
address: string;
|
||||
gateway: string;
|
||||
}
|
||||
|
||||
export interface Service {
|
||||
id: string;
|
||||
description: string;
|
||||
items: ServiceProvider[];
|
||||
}
|
||||
|
||||
export type Services = Service[];
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { Box, Button } from '@mui/material';
|
||||
import { Box } from '@mui/material';
|
||||
import { SimpleModal } from './Modals/SimpleModal';
|
||||
import { ModalFee } from './Modals/ModalFee';
|
||||
|
||||
@@ -20,11 +20,7 @@ export const ConfirmTx: React.FC<{
|
||||
okLabel="Confirm"
|
||||
onOk={onConfirm}
|
||||
onClose={onClose}
|
||||
SecondaryAction={
|
||||
<Button fullWidth sx={{ mt: 1 }} size="large" onClick={onPrev}>
|
||||
Cancel
|
||||
</Button>
|
||||
}
|
||||
onBack={onPrev}
|
||||
>
|
||||
<Box sx={{ mt: 3 }}>
|
||||
{children}
|
||||
|
||||
@@ -98,9 +98,8 @@ export const DelegationsActionsMenu: React.FC<{
|
||||
onActionClick?: (action: DelegationListItemActions) => void;
|
||||
isPending?: DelegationEventKind;
|
||||
disableRedeemingRewards?: boolean;
|
||||
disableDelegateMore?: boolean;
|
||||
disableCompoundRewards?: boolean;
|
||||
}> = ({ disableRedeemingRewards, disableDelegateMore, disableCompoundRewards, onActionClick, isPending }) => {
|
||||
}> = ({ disableRedeemingRewards, disableCompoundRewards, onActionClick, isPending }) => {
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import * as React from 'react';
|
||||
import { useState } from 'react';
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import { Button } from '@mui/material';
|
||||
import { ConfirmationModal } from './ConfirmationModal';
|
||||
|
||||
export default {
|
||||
title: 'Modals/ConfirmationModal',
|
||||
component: ConfirmationModal,
|
||||
} as ComponentMeta<typeof ConfirmationModal>;
|
||||
|
||||
const Template: ComponentStory<typeof ConfirmationModal> = (args) => {
|
||||
const [open, setOpen] = useState(true);
|
||||
return (
|
||||
<>
|
||||
<Button variant="outlined" onClick={() => setOpen(true)}>
|
||||
Open confirmation dialog
|
||||
</Button>
|
||||
<ConfirmationModal {...args} open={open} onClose={() => setOpen(false)} onConfirm={() => setOpen(false)}>
|
||||
Dialog content.
|
||||
</ConfirmationModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
title: 'Confirmation Modal',
|
||||
subTitle: '',
|
||||
fullWidth: true,
|
||||
confirmButton: 'Confirm',
|
||||
maxWidth: 'xs',
|
||||
disabled: false,
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Breakpoint,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
SxProps,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
|
||||
export interface ConfirmationModalProps {
|
||||
open: boolean;
|
||||
onConfirm: () => void;
|
||||
onClose?: () => void;
|
||||
children?: React.ReactNode;
|
||||
title: React.ReactNode | string;
|
||||
subTitle?: React.ReactNode | string;
|
||||
confirmButton: React.ReactNode | string;
|
||||
disabled?: boolean;
|
||||
sx?: SxProps;
|
||||
fullWidth?: boolean;
|
||||
maxWidth?: Breakpoint;
|
||||
}
|
||||
|
||||
export const ConfirmationModal = ({
|
||||
open,
|
||||
onConfirm,
|
||||
onClose,
|
||||
children,
|
||||
title,
|
||||
subTitle,
|
||||
confirmButton,
|
||||
disabled,
|
||||
sx,
|
||||
fullWidth,
|
||||
maxWidth,
|
||||
}: ConfirmationModalProps) => {
|
||||
const Title = (
|
||||
<DialogTitle id="responsive-dialog-title" sx={{ py: 3, pb: 2, fontWeight: 600 }} color="black">
|
||||
{title}
|
||||
{subTitle &&
|
||||
(typeof subTitle === 'string' ? (
|
||||
<Typography fontWeight={400} variant="subtitle1" fontSize={12} color={(t) => t.palette.nym.text.muted}>
|
||||
{subTitle}
|
||||
</Typography>
|
||||
) : (
|
||||
subTitle
|
||||
))}
|
||||
</DialogTitle>
|
||||
);
|
||||
const ConfirmButton =
|
||||
typeof confirmButton === 'string' ? (
|
||||
<Button onClick={onConfirm} variant="contained" fullWidth disabled={disabled} sx={{ py: 1.6 }}>
|
||||
<Typography variant="button" fontSize="large">
|
||||
{confirmButton}
|
||||
</Typography>
|
||||
</Button>
|
||||
) : (
|
||||
confirmButton
|
||||
);
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
aria-labelledby="responsive-dialog-title"
|
||||
maxWidth={maxWidth || 'sm'}
|
||||
sx={{ textAlign: 'center', ...sx }}
|
||||
fullWidth={fullWidth}
|
||||
>
|
||||
{Title}
|
||||
<DialogContent>{children}</DialogContent>
|
||||
<DialogActions sx={{ px: 3, pb: 3 }}>{ConfirmButton}</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { Box, CircularProgress, Modal, Stack, Typography } from '@mui/material';
|
||||
|
||||
const modalStyle = {
|
||||
position: 'absolute' as 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 500,
|
||||
bgcolor: 'background.paper',
|
||||
boxShadow: 24,
|
||||
borderRadius: '16px',
|
||||
p: 4,
|
||||
};
|
||||
|
||||
export const LoadingModal = () => (
|
||||
<Modal open>
|
||||
<Box sx={modalStyle} textAlign="center">
|
||||
<Stack spacing={4} direction="row" alignItems="center">
|
||||
<CircularProgress />
|
||||
<Typography>Please wait...</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
@@ -166,3 +166,31 @@ export const hideCloseIconAndDisplayErrorIcon = () => {
|
||||
</BasePage>
|
||||
);
|
||||
};
|
||||
|
||||
export const withBackButton = () => {
|
||||
const [open, setOpen] = React.useState<boolean>(true);
|
||||
const handleClick = () => setOpen(true);
|
||||
|
||||
return (
|
||||
<BasePage handleClick={handleClick}>
|
||||
<SimpleModal
|
||||
open={open}
|
||||
hideCloseIcon
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
header="This is a modal"
|
||||
okLabel="Primary action"
|
||||
onBack={() => setOpen(false)}
|
||||
>
|
||||
<p>
|
||||
Tempor culpa est magna. Sit tempor cillum culpa sint ipsum nostrud ullamco voluptate exercitation dolore magna
|
||||
elit ut mollit.
|
||||
</p>
|
||||
<ModalDivider />
|
||||
<p>
|
||||
Veniam dolor laborum labore sit reprehenderit enim mollit magna nulla adipisicing fugiat. Est ex irure quis.
|
||||
</p>
|
||||
</SimpleModal>
|
||||
</BasePage>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { Box, Button, Modal, Stack, SxProps, Typography } from '@mui/material';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import ErrorOutline from '@mui/icons-material/ErrorOutline';
|
||||
import { StyledBackButton } from 'src/components/StyledBackButton';
|
||||
import { modalStyle } from './styles';
|
||||
|
||||
export const SimpleModal: React.FC<{
|
||||
@@ -12,12 +13,12 @@ export const SimpleModal: React.FC<{
|
||||
subHeaderStyles?: SxProps;
|
||||
onClose?: () => void;
|
||||
onOk?: () => Promise<void>;
|
||||
onBack?: () => void;
|
||||
header: string;
|
||||
subHeader?: string;
|
||||
okLabel: string;
|
||||
okDisabled?: boolean;
|
||||
sx?: SxProps;
|
||||
SecondaryAction?: React.ReactNode;
|
||||
}> = ({
|
||||
open,
|
||||
hideCloseIcon,
|
||||
@@ -27,11 +28,11 @@ export const SimpleModal: React.FC<{
|
||||
onClose,
|
||||
okDisabled,
|
||||
onOk,
|
||||
onBack,
|
||||
header,
|
||||
subHeader,
|
||||
okLabel,
|
||||
sx,
|
||||
SecondaryAction,
|
||||
children,
|
||||
}) => (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
@@ -57,11 +58,12 @@ export const SimpleModal: React.FC<{
|
||||
|
||||
{children}
|
||||
|
||||
<Button variant="contained" fullWidth size="large" onClick={onOk} disabled={okDisabled} sx={{ mt: 2 }}>
|
||||
{okLabel}
|
||||
</Button>
|
||||
|
||||
{SecondaryAction}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mt: 2 }}>
|
||||
{onBack && <StyledBackButton onBack={onBack} />}
|
||||
<Button variant="contained" fullWidth size="large" onClick={onOk} disabled={okDisabled}>
|
||||
{okLabel}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { FormControl, FormControlLabel, FormLabel, Radio, RadioGroup } from '@mui/material';
|
||||
import { EnumNodeType } from '@nymproject/types';
|
||||
import { TNodeType } from '@nymproject/types';
|
||||
|
||||
export const NodeTypeSelector = ({
|
||||
disabled,
|
||||
@@ -8,10 +8,10 @@ export const NodeTypeSelector = ({
|
||||
setNodeType,
|
||||
}: {
|
||||
disabled: boolean;
|
||||
nodeType: EnumNodeType;
|
||||
setNodeType: (nodeType: EnumNodeType) => void;
|
||||
nodeType: TNodeType;
|
||||
setNodeType: (nodeType: TNodeType) => void;
|
||||
}) => {
|
||||
const handleNodeTypeChange = (e: React.ChangeEvent<HTMLInputElement>) => setNodeType(e.target.value as EnumNodeType);
|
||||
const handleNodeTypeChange = (e: React.ChangeEvent<HTMLInputElement>) => setNodeType(e.target.value as TNodeType);
|
||||
|
||||
return (
|
||||
<FormControl component="fieldset">
|
||||
@@ -24,14 +24,14 @@ export const NodeTypeSelector = ({
|
||||
style={{ display: 'block' }}
|
||||
>
|
||||
<FormControlLabel
|
||||
value={EnumNodeType.mixnode}
|
||||
value="mixnode"
|
||||
control={<Radio color="default" />}
|
||||
label="Mixnode"
|
||||
data-testid="mix-node"
|
||||
disabled={disabled}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value={EnumNodeType.gateway}
|
||||
value="gateway"
|
||||
control={<Radio color="default" />}
|
||||
data-testid="gate-way"
|
||||
label="Gateway"
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Alert, AlertTitle, Stack, Typography } from '@mui/material';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { Alert, AlertTitle, Stack, Typography } from '@mui/material';
|
||||
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { simulateCompoundDelgatorReward, simulateVestingCompoundDelgatorReward } from 'src/requests';
|
||||
import { isGreaterThan } from 'src/utils';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
import { ModalFee } from '../Modals/ModalFee';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
|
||||
export const CompoundModal: React.FC<{
|
||||
open: boolean;
|
||||
@@ -15,7 +15,6 @@ export const CompoundModal: React.FC<{
|
||||
onOk?: (identityKey: string, fee?: FeeDetails) => void;
|
||||
identityKey: string;
|
||||
amount: number;
|
||||
minimum?: number;
|
||||
currency: string;
|
||||
message: string;
|
||||
usesVestingTokens: boolean;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Alert, AlertTitle, Stack, Typography } from '@mui/material';
|
||||
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { simulateClaimDelgatorReward, simulateVestingClaimDelgatorReward } from 'src/requests';
|
||||
import { isGreaterThan } from 'src/utils';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
import { ModalFee } from '../Modals/ModalFee';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
|
||||
export const RedeemModal: React.FC<{
|
||||
open: boolean;
|
||||
@@ -15,7 +15,6 @@ export const RedeemModal: React.FC<{
|
||||
onOk?: (identityKey: string, fee?: FeeDetails) => void;
|
||||
identityKey: string;
|
||||
amount: number;
|
||||
minimum?: number;
|
||||
currency: string;
|
||||
message: string;
|
||||
usesVestingTokens: boolean;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import { Button } from '@mui/material';
|
||||
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||
|
||||
export const StyledBackButton = ({ onBack }: { onBack: () => void }) => (
|
||||
<Button disableFocusRipple size="large" variant="outlined" onClick={onBack}>
|
||||
<ArrowBackIosNewIcon fontSize="small" />
|
||||
</Button>
|
||||
);
|
||||
@@ -20,3 +20,4 @@ export * from './Title';
|
||||
export * from './TokenPoolSelector';
|
||||
export * from './TransactionDetails';
|
||||
export * from './Warning';
|
||||
export * from './Modals/ConfirmationModal';
|
||||
|
||||
@@ -56,11 +56,7 @@ export const DelegationContextProvider: FC<{
|
||||
const [totalRewards, setTotalRewards] = useState<undefined | string>();
|
||||
const [pendingDelegations, setPendingDelegations] = useState<DelegationEvent[]>();
|
||||
|
||||
const addDelegation = async (
|
||||
data: { identity: string; amount: MajorCurrencyAmount },
|
||||
tokenPool: TPoolOption,
|
||||
fee?: FeeDetails,
|
||||
) => {
|
||||
const addDelegation = async (data: { identity: string; amount: MajorCurrencyAmount }, tokenPool: TPoolOption) => {
|
||||
try {
|
||||
let tx;
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { Box, Button } from '@mui/material';
|
||||
import { FeeDetails, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { SimpleModal } from 'src/components/Modals/SimpleModal';
|
||||
import { ModalFee } from 'src/components/Modals/ModalFee';
|
||||
import { ModalListItem } from 'src/components/Modals/ModalListItem';
|
||||
|
||||
export const ConfirmationModal = ({
|
||||
identity,
|
||||
amount,
|
||||
fee,
|
||||
onPrev,
|
||||
onConfirm,
|
||||
}: {
|
||||
identity: string;
|
||||
amount: MajorCurrencyAmount;
|
||||
fee: FeeDetails;
|
||||
onPrev: () => void;
|
||||
onConfirm: () => Promise<void>;
|
||||
}) => (
|
||||
<SimpleModal header="Bond confirmation" open onOk={onConfirm} okLabel="Confirm" hideCloseIcon onBack={onPrev}>
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<ModalListItem label="Mixnode identity" value={identity} />
|
||||
<ModalListItem label="Amount" value={`${amount.amount} ${amount.denom}`} />
|
||||
<ModalFee fee={fee} isLoading={false} />
|
||||
</Box>
|
||||
</SimpleModal>
|
||||
);
|
||||
@@ -0,0 +1,24 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { TNodeType } from '@nymproject/types';
|
||||
import { NodeTypeSelector } from 'src/components';
|
||||
import { GatewayForm } from './GatewayForm';
|
||||
import { MixnodeForm } from './MixnodeForm';
|
||||
|
||||
export const FormHandler = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess: (details: { address: string; amount: string }) => void;
|
||||
onError: (msg?: string) => void;
|
||||
}) => {
|
||||
const [nodeType, setNodeType] = useState<TNodeType>('mixnode');
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<NodeTypeSelector disabled={false} nodeType={nodeType} setNodeType={setNodeType} />
|
||||
{nodeType === 'mixnode' && <MixnodeForm disabled={false} onError={onError} onSuccess={onSuccess} />}
|
||||
{nodeType === 'gateway' && <GatewayForm disabled={false} onError={onError} onSuccess={onSuccess} />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
+108
-168
@@ -1,45 +1,34 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { Box, Button, Checkbox, CircularProgress, FormControl, FormControlLabel, Grid, TextField } from '@mui/material';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import {
|
||||
Gateway,
|
||||
MixNode,
|
||||
EnumNodeType,
|
||||
MajorCurrencyAmount,
|
||||
CurrencyDenom,
|
||||
TransactionExecuteResult,
|
||||
} from '@nymproject/types';
|
||||
import { Box, Button, Checkbox, CircularProgress, FormControl, FormControlLabel, Grid, TextField } from '@mui/material';
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||
import { TBondArgs } from 'src/types';
|
||||
import { CurrencyDenom, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { bondGateway, simulateBondGateway, simulateVestingBondGateway, vestingBondGateway } from 'src/requests';
|
||||
import { checkHasEnoughFunds, checkHasEnoughLockedTokens } from 'src/utils';
|
||||
import { NodeTypeSelector } from '../../components/NodeTypeSelector';
|
||||
import { bond, vestingBond } from '../../requests';
|
||||
import { validationSchema } from './validationSchema';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { Fee, TokenPoolSelector } from '../../components';
|
||||
import { Fee, TokenPoolSelector } from '../../../components';
|
||||
import { AppContext } from '../../../context/main';
|
||||
import { gatewayValidationSchema } from '../validationSchema';
|
||||
import { ConfirmationModal } from './ConfirmationModal';
|
||||
|
||||
type TBondFormFields = {
|
||||
withAdvancedOptions: boolean;
|
||||
nodeType: EnumNodeType;
|
||||
tokenPool: string;
|
||||
ownerSignature: string;
|
||||
identityKey: string;
|
||||
sphinxKey: string;
|
||||
profitMarginPercent: number;
|
||||
amount: MajorCurrencyAmount;
|
||||
host: string;
|
||||
version: string;
|
||||
location?: string;
|
||||
location: string;
|
||||
mixPort: number;
|
||||
verlocPort: number;
|
||||
clientsPort: number;
|
||||
httpApiPort: number;
|
||||
};
|
||||
|
||||
const defaultValues = {
|
||||
withAdvancedOptions: false,
|
||||
nodeType: EnumNodeType.mixnode,
|
||||
tokenPool: 'balance',
|
||||
identityKey: '',
|
||||
sphinxKey: '',
|
||||
@@ -47,35 +36,12 @@ const defaultValues = {
|
||||
amount: { amount: '', denom: 'NYM' as CurrencyDenom },
|
||||
host: '',
|
||||
version: '',
|
||||
profitMarginPercent: 10,
|
||||
location: undefined,
|
||||
location: '',
|
||||
mixPort: 1789,
|
||||
verlocPort: 1790,
|
||||
httpApiPort: 8000,
|
||||
clientsPort: 9000,
|
||||
};
|
||||
|
||||
const formatData = (data: TBondFormFields): MixNode | Gateway => {
|
||||
const payload: { [key: string]: any } = {
|
||||
identity_key: data.identityKey,
|
||||
sphinx_key: data.sphinxKey,
|
||||
host: data.host,
|
||||
version: data.version,
|
||||
mix_port: data.mixPort,
|
||||
profit_margin_percent: data.profitMarginPercent,
|
||||
};
|
||||
|
||||
if (data.nodeType === EnumNodeType.mixnode) {
|
||||
payload.verloc_port = data.verlocPort;
|
||||
payload.http_api_port = data.httpApiPort;
|
||||
return payload as MixNode;
|
||||
}
|
||||
payload.clients_port = data.clientsPort;
|
||||
payload.location = data.location;
|
||||
return payload as Gateway;
|
||||
};
|
||||
|
||||
export const BondForm = ({
|
||||
export const GatewayForm = ({
|
||||
disabled,
|
||||
onError,
|
||||
onSuccess,
|
||||
@@ -91,22 +57,23 @@ export const BondForm = ({
|
||||
watch,
|
||||
reset,
|
||||
setError,
|
||||
getValues,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<TBondFormFields>({
|
||||
resolver: yupResolver(validationSchema),
|
||||
resolver: yupResolver(gatewayValidationSchema),
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
|
||||
const { fee, getFee, resetFeeState } = useGetFee();
|
||||
|
||||
useEffect(() => {
|
||||
reset();
|
||||
}, [clientDetails]);
|
||||
|
||||
const watchNodeType = watch('nodeType', defaultValues.nodeType);
|
||||
const watchAdvancedOptions = watch('withAdvancedOptions', defaultValues.withAdvancedOptions);
|
||||
|
||||
const onSubmit = async (data: TBondFormFields, cb: (data: TBondArgs) => Promise<TransactionExecuteResult>) => {
|
||||
const handleValidateAndGetFee = async (data: TBondFormFields) => {
|
||||
if (data.tokenPool === 'balance' && !(await checkHasEnoughFunds(data.amount.amount || ''))) {
|
||||
return setError('amount.amount', { message: 'Not enough funds in wallet' });
|
||||
}
|
||||
@@ -115,43 +82,73 @@ export const BondForm = ({
|
||||
return setError('amount.amount', { message: 'Not enough locked tokens' });
|
||||
}
|
||||
|
||||
const formattedData = formatData(data);
|
||||
|
||||
return cb({
|
||||
type: data.nodeType,
|
||||
ownerSignature: data.ownerSignature,
|
||||
[data.nodeType]: formattedData,
|
||||
pledge: data.amount,
|
||||
} as TBondArgs)
|
||||
.then(async () => {
|
||||
if (data.tokenPool === 'balance') {
|
||||
await userBalance.fetchBalance();
|
||||
} else {
|
||||
await userBalance.fetchTokenAllocation();
|
||||
}
|
||||
onSuccess({ address: data.identityKey, amount: data.amount.amount });
|
||||
})
|
||||
.catch((e) => {
|
||||
onError(e);
|
||||
try {
|
||||
await getFee(data.tokenPool === 'locked' ? simulateVestingBondGateway : simulateBondGateway, {
|
||||
ownerSignature: data.ownerSignature,
|
||||
gateway: {
|
||||
identity_key: data.identityKey,
|
||||
sphinx_key: data.sphinxKey,
|
||||
host: data.host,
|
||||
version: data.version,
|
||||
mix_port: data.mixPort,
|
||||
location: data.location,
|
||||
clients_port: data.clientsPort,
|
||||
},
|
||||
pledge: data.amount,
|
||||
});
|
||||
} catch (e) {
|
||||
onError(e as string);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const onSubmit = async (data: TBondFormFields) => {
|
||||
const payload = {
|
||||
ownerSignature: data.ownerSignature,
|
||||
gateway: {
|
||||
identity_key: data.identityKey,
|
||||
sphinx_key: data.sphinxKey,
|
||||
host: data.host,
|
||||
version: data.version,
|
||||
mix_port: data.mixPort,
|
||||
location: data.location,
|
||||
clients_port: data.clientsPort,
|
||||
},
|
||||
pledge: data.amount,
|
||||
fee: fee?.fee,
|
||||
};
|
||||
try {
|
||||
if (data.tokenPool === 'balance') {
|
||||
await bondGateway(payload);
|
||||
await userBalance.fetchBalance();
|
||||
}
|
||||
|
||||
if (data.tokenPool === 'locked') {
|
||||
await vestingBondGateway(payload);
|
||||
await userBalance.fetchTokenAllocation();
|
||||
}
|
||||
|
||||
onSuccess({ address: payload.gateway.identity_key, amount: payload.pledge.amount });
|
||||
} catch (e) {
|
||||
onError(e as string);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl fullWidth>
|
||||
<Box sx={{ p: 3 }}>
|
||||
{isSubmitting && <LoadingModal />}
|
||||
|
||||
{fee && !isSubmitting && (
|
||||
<ConfirmationModal
|
||||
identity={getValues('identityKey')}
|
||||
amount={getValues('amount')}
|
||||
fee={fee}
|
||||
onPrev={resetFeeState}
|
||||
onConfirm={handleSubmit(onSubmit)}
|
||||
/>
|
||||
)}
|
||||
<Box>
|
||||
<Grid container spacing={3}>
|
||||
<Grid container item justifyContent="space-between">
|
||||
<Grid item>
|
||||
<NodeTypeSelector
|
||||
nodeType={watchNodeType}
|
||||
setNodeType={(nodeType) => {
|
||||
setValue('nodeType', nodeType);
|
||||
if (nodeType === EnumNodeType.mixnode) setValue('location', undefined);
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
{...register('identityKey')}
|
||||
@@ -213,40 +210,20 @@ export const BondForm = ({
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{watchNodeType === EnumNodeType.mixnode && (
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
{...register('profitMarginPercent')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="profitMarginPercent"
|
||||
name="profitMarginPercent"
|
||||
label="Profit percentage"
|
||||
fullWidth
|
||||
error={!!errors.profitMarginPercent}
|
||||
helperText={errors.profitMarginPercent ? errors.profitMarginPercent.message : 'Default is 10%'}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{/* if it's a gateway - get location */}
|
||||
{watchNodeType === EnumNodeType.gateway && (
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
{...register('location')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="location"
|
||||
name="location"
|
||||
label="Location"
|
||||
fullWidth
|
||||
error={!!errors.location}
|
||||
helperText={errors.location?.message}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
{...register('location')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="location"
|
||||
name="location"
|
||||
label="Location"
|
||||
fullWidth
|
||||
error={!!errors.location}
|
||||
helperText={errors.location?.message}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
@@ -291,12 +268,6 @@ export const BondForm = ({
|
||||
setValue('clientsPort', defaultValues.clientsPort, {
|
||||
shouldValidate: true,
|
||||
});
|
||||
setValue('verlocPort', defaultValues.verlocPort, {
|
||||
shouldValidate: true,
|
||||
});
|
||||
setValue('httpApiPort', defaultValues.httpApiPort, {
|
||||
shouldValidate: true,
|
||||
});
|
||||
setValue('withAdvancedOptions', false);
|
||||
} else {
|
||||
setValue('withAdvancedOptions', true);
|
||||
@@ -322,55 +293,24 @@ export const BondForm = ({
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
{watchNodeType === EnumNodeType.mixnode ? (
|
||||
<>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
{...register('verlocPort', { valueAsNumber: true })}
|
||||
variant="outlined"
|
||||
id="verlocPort"
|
||||
name="verlocPort"
|
||||
label="Verloc Port"
|
||||
fullWidth
|
||||
error={!!errors.verlocPort}
|
||||
helperText={errors.verlocPort?.message && 'A valid port value is required'}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
{...register('httpApiPort', { valueAsNumber: true })}
|
||||
variant="outlined"
|
||||
id="httpApiPort"
|
||||
name="httpApiPort"
|
||||
label="HTTP API Port"
|
||||
fullWidth
|
||||
error={!!errors.httpApiPort}
|
||||
helperText={errors.httpApiPort?.message && 'A valid port value is required'}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
</>
|
||||
) : (
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
{...register('clientsPort', { valueAsNumber: true })}
|
||||
variant="outlined"
|
||||
id="clientsPort"
|
||||
name="clientsPort"
|
||||
label="client WS API Port"
|
||||
fullWidth
|
||||
error={!!errors.clientsPort}
|
||||
helperText={errors.clientsPort?.message && 'A valid port value is required'}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
{...register('clientsPort', { valueAsNumber: true })}
|
||||
variant="outlined"
|
||||
id="clientsPort"
|
||||
name="clientsPort"
|
||||
label="client WS API Port"
|
||||
fullWidth
|
||||
error={!!errors.clientsPort}
|
||||
helperText={errors.clientsPort?.message && 'A valid port value is required'}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
{!disabled ? <Fee feeType={EnumNodeType.mixnode ? 'BondMixnode' : 'BondGateway'} /> : <div />}
|
||||
{!disabled ? <Fee feeType="BondGateway" /> : <div />}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
@@ -390,7 +330,7 @@ export const BondForm = ({
|
||||
type="submit"
|
||||
data-testid="submit-button"
|
||||
disableElevation
|
||||
onClick={handleSubmit((data) => onSubmit(data, data.tokenPool === 'balance' ? bond : vestingBond))}
|
||||
onClick={handleSubmit(handleValidateAndGetFee)}
|
||||
endIcon={isSubmitting && <CircularProgress size={20} />}
|
||||
size="large"
|
||||
>
|
||||
@@ -0,0 +1,361 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { Box, Button, Checkbox, CircularProgress, FormControl, FormControlLabel, Grid, TextField } from '@mui/material';
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||
import { CurrencyDenom, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { checkHasEnoughFunds, checkHasEnoughLockedTokens } from 'src/utils';
|
||||
import { TokenPoolSelector } from '../../../components';
|
||||
import { AppContext } from '../../../context/main';
|
||||
import { bondMixNode, simulateBondMixnode, simulateVestingBondMixnode, vestingBondMixNode } from '../../../requests';
|
||||
import { mixnodeValidationSchema } from '../validationSchema';
|
||||
import { ConfirmationModal } from './ConfirmationModal';
|
||||
|
||||
type TBondFormFields = {
|
||||
withAdvancedOptions: boolean;
|
||||
tokenPool: string;
|
||||
ownerSignature: string;
|
||||
identityKey: string;
|
||||
sphinxKey: string;
|
||||
profitMarginPercent: number;
|
||||
amount: MajorCurrencyAmount;
|
||||
host: string;
|
||||
version: string;
|
||||
mixPort: number;
|
||||
verlocPort: number;
|
||||
httpApiPort: number;
|
||||
};
|
||||
|
||||
const defaultValues = {
|
||||
withAdvancedOptions: false,
|
||||
tokenPool: 'balance',
|
||||
identityKey: '',
|
||||
sphinxKey: '',
|
||||
ownerSignature: '',
|
||||
amount: { amount: '', denom: 'NYM' as CurrencyDenom },
|
||||
host: '',
|
||||
version: '',
|
||||
profitMarginPercent: 10,
|
||||
mixPort: 1789,
|
||||
verlocPort: 1790,
|
||||
httpApiPort: 8000,
|
||||
};
|
||||
|
||||
export const MixnodeForm = ({
|
||||
disabled,
|
||||
onError,
|
||||
onSuccess,
|
||||
}: {
|
||||
disabled: boolean;
|
||||
onError: (message?: string) => void;
|
||||
onSuccess: (details: { address: string; amount: string }) => void;
|
||||
}) => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
getValues,
|
||||
watch,
|
||||
reset,
|
||||
setError,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<TBondFormFields>({
|
||||
resolver: yupResolver(mixnodeValidationSchema),
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
|
||||
const { fee, getFee, resetFeeState } = useGetFee();
|
||||
|
||||
useEffect(() => {
|
||||
reset();
|
||||
}, [clientDetails]);
|
||||
|
||||
const watchAdvancedOptions = watch('withAdvancedOptions', defaultValues.withAdvancedOptions);
|
||||
|
||||
const handleValidateAndGetFee = async (data: TBondFormFields) => {
|
||||
if (data.tokenPool === 'balance' && !(await checkHasEnoughFunds(data.amount.amount || ''))) {
|
||||
return setError('amount.amount', { message: 'Not enough funds in wallet' });
|
||||
}
|
||||
|
||||
if (data.tokenPool === 'locked' && !(await checkHasEnoughLockedTokens(data.amount.amount || ''))) {
|
||||
return setError('amount.amount', { message: 'Not enough locked tokens' });
|
||||
}
|
||||
|
||||
try {
|
||||
await getFee(data.tokenPool === 'locked' ? simulateVestingBondMixnode : simulateBondMixnode, {
|
||||
ownerSignature: data.ownerSignature,
|
||||
mixnode: {
|
||||
identity_key: data.identityKey,
|
||||
sphinx_key: data.sphinxKey,
|
||||
host: data.host,
|
||||
version: data.version,
|
||||
mix_port: data.mixPort,
|
||||
profit_margin_percent: data.profitMarginPercent,
|
||||
verloc_port: data.verlocPort,
|
||||
http_api_port: data.httpApiPort,
|
||||
},
|
||||
pledge: data.amount,
|
||||
});
|
||||
} catch (e) {
|
||||
onError(e as string);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const onSubmit = async (data: TBondFormFields) => {
|
||||
const payload = {
|
||||
ownerSignature: data.ownerSignature,
|
||||
mixnode: {
|
||||
identity_key: data.identityKey,
|
||||
sphinx_key: data.sphinxKey,
|
||||
host: data.host,
|
||||
version: data.version,
|
||||
mix_port: data.mixPort,
|
||||
profit_margin_percent: data.profitMarginPercent,
|
||||
verloc_port: data.verlocPort,
|
||||
http_api_port: data.httpApiPort,
|
||||
},
|
||||
pledge: data.amount,
|
||||
fee: fee?.fee,
|
||||
};
|
||||
try {
|
||||
if (data.tokenPool === 'balance') {
|
||||
await bondMixNode(payload);
|
||||
await userBalance.fetchBalance();
|
||||
}
|
||||
|
||||
if (data.tokenPool === 'locked') {
|
||||
await vestingBondMixNode(payload);
|
||||
await userBalance.fetchTokenAllocation();
|
||||
}
|
||||
|
||||
onSuccess({ address: payload.mixnode.identity_key, amount: payload.pledge.amount });
|
||||
} catch (e) {
|
||||
onError(e as string);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isSubmitting && <LoadingModal />}
|
||||
|
||||
{fee && !isSubmitting && (
|
||||
<ConfirmationModal
|
||||
identity={getValues('identityKey')}
|
||||
amount={getValues('amount')}
|
||||
fee={fee}
|
||||
onPrev={resetFeeState}
|
||||
onConfirm={handleSubmit(onSubmit)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormControl fullWidth>
|
||||
<Box>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
{...register('identityKey')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="identityKey"
|
||||
name="identityKey"
|
||||
label="Identity key"
|
||||
fullWidth
|
||||
error={!!errors.identityKey}
|
||||
helperText={errors.identityKey?.message}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
{...register('sphinxKey')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="sphinxKey"
|
||||
name="sphinxKey"
|
||||
label="Sphinx key"
|
||||
error={!!errors.sphinxKey}
|
||||
helperText={errors.sphinxKey?.message}
|
||||
fullWidth
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={12}>
|
||||
<TextField
|
||||
{...register('ownerSignature')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="ownerSignature"
|
||||
name="ownerSignature"
|
||||
label="Owner signature"
|
||||
fullWidth
|
||||
error={!!errors.ownerSignature}
|
||||
helperText={errors.ownerSignature?.message}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{userBalance.originalVesting && (
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TokenPoolSelector onSelect={(pool) => setValue('tokenPool', pool)} disabled={disabled} />
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12} sm={6}>
|
||||
<CurrencyFormField
|
||||
showCoinMark
|
||||
required
|
||||
fullWidth
|
||||
label="Amount"
|
||||
onChanged={(val) => setValue('amount', val, { shouldValidate: true })}
|
||||
denom={clientDetails?.denom}
|
||||
validationError={errors.amount?.amount?.message}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
{...register('profitMarginPercent')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="profitMarginPercent"
|
||||
name="profitMarginPercent"
|
||||
label="Profit percentage"
|
||||
fullWidth
|
||||
error={!!errors.profitMarginPercent}
|
||||
helperText={errors.profitMarginPercent ? errors.profitMarginPercent.message : 'Default is 10%'}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
{...register('host')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="host"
|
||||
name="host"
|
||||
label="Host"
|
||||
fullWidth
|
||||
error={!!errors.host}
|
||||
helperText={errors.host?.message}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
{...register('version')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="version"
|
||||
name="version"
|
||||
label="Version"
|
||||
fullWidth
|
||||
error={!!errors.version}
|
||||
helperText={errors.version?.message}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={watchAdvancedOptions}
|
||||
onChange={() => {
|
||||
if (watchAdvancedOptions) {
|
||||
setValue('mixPort', defaultValues.mixPort, {
|
||||
shouldValidate: true,
|
||||
});
|
||||
setValue('httpApiPort', defaultValues.httpApiPort, {
|
||||
shouldValidate: true,
|
||||
});
|
||||
setValue('withAdvancedOptions', false);
|
||||
} else {
|
||||
setValue('withAdvancedOptions', true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Use advanced options"
|
||||
/>
|
||||
</Grid>
|
||||
{watchAdvancedOptions && (
|
||||
<>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
{...register('mixPort', { valueAsNumber: true })}
|
||||
variant="outlined"
|
||||
id="mixPort"
|
||||
name="mixPort"
|
||||
label="Mix Port"
|
||||
fullWidth
|
||||
error={!!errors.mixPort}
|
||||
helperText={errors.mixPort?.message && 'A valid port value is required'}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
{...register('verlocPort', { valueAsNumber: true })}
|
||||
variant="outlined"
|
||||
id="verlocPort"
|
||||
name="verlocPort"
|
||||
label="Verloc Port"
|
||||
fullWidth
|
||||
error={!!errors.verlocPort}
|
||||
helperText={errors.verlocPort?.message && 'A valid port value is required'}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
{...register('httpApiPort', { valueAsNumber: true })}
|
||||
variant="outlined"
|
||||
id="httpApiPort"
|
||||
name="httpApiPort"
|
||||
label="HTTP API Port"
|
||||
fullWidth
|
||||
error={!!errors.httpApiPort}
|
||||
helperText={errors.httpApiPort?.message && 'A valid port value is required'}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
padding: 3,
|
||||
pt: 0,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
disabled={isSubmitting || disabled}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
data-testid="submit-button"
|
||||
disableElevation
|
||||
onClick={handleSubmit(handleValidateAndGetFee)}
|
||||
endIcon={isSubmitting && <CircularProgress size={20} />}
|
||||
size="large"
|
||||
>
|
||||
Bond
|
||||
</Button>
|
||||
</Box>
|
||||
</FormControl>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { SuccessReponse, TransactionDetails } from '../../components';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
|
||||
import { SuccessReponse, TransactionDetails } from '../../../components';
|
||||
import { AppContext } from '../../../context/main';
|
||||
import { useCheckOwnership } from '../../../hooks/useCheckOwnership';
|
||||
|
||||
export const SuccessView: React.FC<{ details?: { amount: string; address: string } }> = ({ details }) => {
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
@@ -1,21 +1,20 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Alert, Box, Button, CircularProgress } from '@mui/material';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { BondForm } from './BondForm';
|
||||
import { SuccessView } from './SuccessView';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { NymCard } from '../../components';
|
||||
import { EnumRequestStatus, RequestStatus } from '../../components/RequestStatus';
|
||||
import { unbond, vestingUnbond } from '../../requests';
|
||||
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
|
||||
import { PageLayout } from '../../layouts';
|
||||
import { FormHandler } from './components/FormHandler';
|
||||
import { SuccessView } from './components/SuccessView';
|
||||
|
||||
export const Bond = () => {
|
||||
const [status, setStatus] = useState(EnumRequestStatus.initial);
|
||||
const [status, setStatus] = useState<EnumRequestStatus>(EnumRequestStatus.initial);
|
||||
const [error, setError] = useState<string>();
|
||||
const [successDetails, setSuccessDetails] = useState<{ amount: string; address: string }>();
|
||||
|
||||
const { checkOwnership, ownership, isLoading } = useCheckOwnership();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (status === EnumRequestStatus.initial) {
|
||||
@@ -28,7 +27,7 @@ export const Bond = () => {
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<NymCard title="Bond" subheader="Bond a node or gateway" noPadding>
|
||||
<NymCard title="Bond" subheader="Bond a mixnode or gateway" noPadding>
|
||||
{status === EnumRequestStatus.initial && (
|
||||
<Box sx={{ px: 3, mb: 1 }}>
|
||||
<Alert severity="warning">Always ensure you leave yourself enough funds to UNBOND</Alert>
|
||||
@@ -41,20 +40,7 @@ export const Bond = () => {
|
||||
action={
|
||||
<Button
|
||||
disabled={status === EnumRequestStatus.loading}
|
||||
onClick={async () => {
|
||||
setStatus(EnumRequestStatus.loading);
|
||||
try {
|
||||
if (ownership.vestingPledge) {
|
||||
await vestingUnbond(ownership.nodeType!);
|
||||
} else {
|
||||
await unbond(ownership.nodeType!);
|
||||
}
|
||||
} catch (e) {
|
||||
enqueueSnackbar(`Failed to unbond ${ownership.nodeType}}`, { variant: 'error' });
|
||||
} finally {
|
||||
setStatus(EnumRequestStatus.initial);
|
||||
}
|
||||
}}
|
||||
onClick={() => navigate('/unbond')}
|
||||
data-testid="unBond"
|
||||
color="inherit"
|
||||
>
|
||||
@@ -78,16 +64,15 @@ export const Bond = () => {
|
||||
</Box>
|
||||
)}
|
||||
{status === EnumRequestStatus.initial && !ownership.hasOwnership && !isLoading && (
|
||||
<BondForm
|
||||
onError={(e?: string) => {
|
||||
setError(e);
|
||||
setStatus(EnumRequestStatus.error);
|
||||
}}
|
||||
<FormHandler
|
||||
onSuccess={(details) => {
|
||||
setSuccessDetails(details);
|
||||
setStatus(EnumRequestStatus.success);
|
||||
setSuccessDetails(details);
|
||||
}}
|
||||
onError={(err) => {
|
||||
setStatus(EnumRequestStatus.error);
|
||||
setError(err);
|
||||
}}
|
||||
disabled={ownership?.hasOwnership}
|
||||
/>
|
||||
)}
|
||||
{(status === EnumRequestStatus.error || status === EnumRequestStatus.success) && (
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
validateVersion,
|
||||
} from '../../utils';
|
||||
|
||||
export const validationSchema = Yup.object().shape({
|
||||
export const mixnodeValidationSchema = Yup.object().shape({
|
||||
identityKey: Yup.string()
|
||||
.required('An indentity key is required')
|
||||
.test('valid-id-key', 'A valid identity key is required', (value) => validateKey(value || '', 32)),
|
||||
@@ -37,6 +37,54 @@ export const validationSchema = Yup.object().shape({
|
||||
}),
|
||||
}),
|
||||
|
||||
host: Yup.string()
|
||||
.required('A host is required')
|
||||
.test('valid-host', 'A valid host is required', (value) => (value ? isValidHostname(value) : false)),
|
||||
|
||||
version: Yup.string()
|
||||
.required('A version is required')
|
||||
.test('valid-version', 'A valid version is required', (value) => (value ? validateVersion(value) : false)),
|
||||
|
||||
mixPort: Yup.number()
|
||||
.required('A mixport is required')
|
||||
.test('valid-mixport', 'A valid mixport is required', (value) => (value ? validateRawPort(value) : false)),
|
||||
|
||||
verlocPort: Yup.number()
|
||||
.required('A verloc port is required')
|
||||
.test('valid-verloc', 'A valid verloc port is required', (value) => (value ? validateRawPort(value) : false)),
|
||||
|
||||
httpApiPort: Yup.number()
|
||||
.required('A http-api port is required')
|
||||
.test('valid-http', 'A valid http-api port is required', (value) => (value ? validateRawPort(value) : false)),
|
||||
});
|
||||
|
||||
export const gatewayValidationSchema = Yup.object().shape({
|
||||
identityKey: Yup.string()
|
||||
.required('An indentity key is required')
|
||||
.test('valid-id-key', 'A valid identity key is required', (value) => validateKey(value || '', 32)),
|
||||
|
||||
sphinxKey: Yup.string()
|
||||
.required('A sphinx key is required')
|
||||
.test('valid-sphinx-key', 'A valid sphinx key is required', (value) => validateKey(value || '', 32)),
|
||||
|
||||
ownerSignature: Yup.string()
|
||||
.required('Signature is required')
|
||||
.test('valid-signature', 'A valid signature is required', (value) => validateKey(value || '', 64)),
|
||||
|
||||
amount: Yup.object().shape({
|
||||
amount: Yup.string()
|
||||
.required('An amount is required')
|
||||
.test('valid-amount', 'Pledge error', async function isValidAmount(this, value) {
|
||||
const isValid = await validateAmount(value || '', '100');
|
||||
|
||||
if (!isValid) {
|
||||
return this.createError({ message: 'A valid amount is required (min 100)' });
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
}),
|
||||
|
||||
host: Yup.string()
|
||||
.required('A host is required')
|
||||
.test('valid-host', 'A valid host is required', (value) => (value ? isValidHostname(value) : false)),
|
||||
@@ -60,14 +108,6 @@ export const validationSchema = Yup.object().shape({
|
||||
.required('A mixport is required')
|
||||
.test('valid-mixport', 'A valid mixport is required', (value) => (value ? validateRawPort(value) : false)),
|
||||
|
||||
verlocPort: Yup.number()
|
||||
.required('A verloc port is required')
|
||||
.test('valid-verloc', 'A valid verloc port is required', (value) => (value ? validateRawPort(value) : false)),
|
||||
|
||||
httpApiPort: Yup.number()
|
||||
.required('A http-api port is required')
|
||||
.test('valid-http', 'A valid http-api port is required', (value) => (value ? validateRawPort(value) : false)),
|
||||
|
||||
clientsPort: Yup.number()
|
||||
.required('A clients port is required')
|
||||
.test('valid-clients', 'A valid clients port is required', (value) => (value ? validateRawPort(value) : false)),
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useContext, useEffect } from 'react';
|
||||
import { Box, Button, CircularProgress, FormControl, Grid, InputAdornment, TextField } from '@mui/material';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { EnumNodeType, MajorCurrencyAmount, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { MajorCurrencyAmount, TNodeType, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { TDelegateArgs } from '../../types';
|
||||
import { validationSchema } from './validationSchema';
|
||||
import { AppContext } from '../../context/main';
|
||||
@@ -14,14 +14,14 @@ type TDelegateForm = {
|
||||
identity: string;
|
||||
amount: MajorCurrencyAmount;
|
||||
tokenPool: string;
|
||||
type: EnumNodeType;
|
||||
type: TNodeType;
|
||||
};
|
||||
|
||||
const defaultValues: TDelegateForm = {
|
||||
identity: '',
|
||||
amount: { amount: '', denom: 'NYM' },
|
||||
tokenPool: 'balance',
|
||||
type: EnumNodeType.mixnode,
|
||||
type: 'mixnode',
|
||||
};
|
||||
|
||||
export const DelegateForm = ({
|
||||
|
||||
@@ -410,7 +410,7 @@ export const Delegation: FC = () => {
|
||||
open={Boolean(saturationError)}
|
||||
onClose={() => setSaturationError(undefined)}
|
||||
header={`Node saturation: ${Math.round(saturationError.saturation * 100000) / 1000}%`}
|
||||
subHeader={'This node is over saturated. Choose a new mix node to delegate to and start compounding rewards.'}
|
||||
subHeader="This node is over saturated. Choose a new mix node to delegate to and start compounding rewards."
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { SimpleModal } from 'src/components/Modals/SimpleModal';
|
||||
|
||||
export const ConfirmationModal = ({
|
||||
txUrl,
|
||||
message,
|
||||
success,
|
||||
onClose,
|
||||
}: {
|
||||
txUrl?: string;
|
||||
message?: string;
|
||||
success: boolean;
|
||||
onClose: () => void;
|
||||
}) => (
|
||||
<SimpleModal
|
||||
open
|
||||
header={success ? 'Successfully Unbonded' : 'Unbonding failed'}
|
||||
displayErrorIcon={!success}
|
||||
onOk={async () => onClose()}
|
||||
okLabel="Done"
|
||||
hideCloseIcon
|
||||
sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1 }}
|
||||
>
|
||||
{message && <Typography variant="caption">Error: {message}</Typography>}
|
||||
{txUrl && <Link href={txUrl} target="_blank" sx={{ ml: 1 }} text="View on blockchain" />}
|
||||
</SimpleModal>
|
||||
);
|
||||
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { Box, Button } from '@mui/material';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { simulateUnbondGateway, simulateVestingUnbondGateway } from 'src/requests';
|
||||
import { ConfirmTx } from 'src/components/ConfirmTX';
|
||||
|
||||
export const UnbondGateway = ({
|
||||
isWithVestingTokens,
|
||||
onConfirm,
|
||||
onError,
|
||||
}: {
|
||||
isWithVestingTokens: boolean;
|
||||
onConfirm: (isWithVestingTokens: boolean, fee: FeeDetails) => Promise<void>;
|
||||
onError: (err?: string) => void;
|
||||
}) => {
|
||||
const { fee, getFee, resetFeeState } = useGetFee();
|
||||
|
||||
const handleGetFee = async () => {
|
||||
try {
|
||||
if (isWithVestingTokens) await getFee(simulateVestingUnbondGateway, {});
|
||||
if (!isWithVestingTokens) await getFee(simulateUnbondGateway, {});
|
||||
} catch (e) {
|
||||
onError(e as string);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 3, display: 'flex', justifyContent: 'flex-end' }}>
|
||||
{fee && (
|
||||
<ConfirmTx
|
||||
open
|
||||
fee={fee}
|
||||
header="Unbond gateway details"
|
||||
onPrev={resetFeeState}
|
||||
onClose={resetFeeState}
|
||||
onConfirm={async () => {
|
||||
onConfirm(isWithVestingTokens, fee);
|
||||
resetFeeState();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Button size="large" variant="contained" disableElevation onClick={handleGetFee}>
|
||||
Unbond gateway
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { Box, Button } from '@mui/material';
|
||||
import { ConfirmTx } from 'src/components/ConfirmTX';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { simulateUnbondMixnode, simulateVestingUnbondMixnode } from 'src/requests';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
|
||||
export const UnbondMixnode = ({
|
||||
isWithVestingTokens,
|
||||
onConfirm,
|
||||
onError,
|
||||
}: {
|
||||
isWithVestingTokens: boolean;
|
||||
onConfirm: (isWithVestingTokens: boolean, fee: FeeDetails) => Promise<void>;
|
||||
onError: (err?: string) => void;
|
||||
}) => {
|
||||
const { fee, getFee, resetFeeState } = useGetFee();
|
||||
|
||||
const handleGetFee = async () => {
|
||||
try {
|
||||
if (isWithVestingTokens) await getFee(simulateVestingUnbondMixnode, {});
|
||||
if (!isWithVestingTokens) await getFee(simulateUnbondMixnode, {});
|
||||
} catch (e) {
|
||||
onError(e as string);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 3, display: 'flex', justifyContent: 'flex-end' }}>
|
||||
{fee && (
|
||||
<ConfirmTx
|
||||
open
|
||||
fee={fee}
|
||||
header="Unbond mixnode details"
|
||||
onPrev={resetFeeState}
|
||||
onClose={resetFeeState}
|
||||
onConfirm={async () => {
|
||||
onConfirm(isWithVestingTokens, fee);
|
||||
resetFeeState();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Button size="large" variant="contained" disableElevation onClick={handleGetFee}>
|
||||
Unbond mixnode
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,87 +1,191 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Alert, Box, Button, CircularProgress } from '@mui/material';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { Fee, NymCard } from '../../components';
|
||||
import { Alert, Button } from '@mui/material';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
||||
import { AppContext, urls } from 'src/context';
|
||||
import { unbondGateway, unbondMixNode, vestingUnbondGateway, vestingUnbondMixnode } from 'src/requests';
|
||||
import { EnumNodeType } from 'src/types';
|
||||
import { NymCard } from '../../components';
|
||||
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { unbond, vestingUnbond } from '../../requests';
|
||||
import { PageLayout } from '../../layouts';
|
||||
import { ConfirmationModal } from './ConfirmationModal';
|
||||
import { UnbondGateway } from './UnbondGateway';
|
||||
import { UnbondMixnode } from './UnbondMixnode';
|
||||
|
||||
export const Unbond = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { network } = useContext(AppContext);
|
||||
const { checkOwnership, ownership } = useCheckOwnership();
|
||||
const { userBalance, getBondDetails } = useContext(AppContext);
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [confirmationDetails, setConfirmationDetails] = useState<
|
||||
{ success: boolean; txUrl?: string; message?: string } | undefined
|
||||
>();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const initialiseForm = async () => {
|
||||
await checkOwnership();
|
||||
};
|
||||
initialiseForm();
|
||||
}, [ownership.hasOwnership, checkOwnership]);
|
||||
}, [checkOwnership]);
|
||||
|
||||
const handleUnbondMixnode = async (isWithVestingTokens: boolean, fee: FeeDetails) => {
|
||||
let tx;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (isWithVestingTokens) tx = await vestingUnbondMixnode(fee?.fee);
|
||||
if (!isWithVestingTokens) tx = await unbondMixNode(fee?.fee);
|
||||
setConfirmationDetails({
|
||||
success: true,
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
} catch (err) {
|
||||
setConfirmationDetails({
|
||||
success: false,
|
||||
message: err as string,
|
||||
});
|
||||
} finally {
|
||||
await checkOwnership();
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnbondGateway = async (isWithVestingTokens: boolean, fee: FeeDetails) => {
|
||||
let tx;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (isWithVestingTokens) tx = await vestingUnbondGateway(fee?.fee);
|
||||
if (!isWithVestingTokens) tx = await unbondGateway(fee?.fee);
|
||||
setConfirmationDetails({
|
||||
success: true,
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
} catch (err) {
|
||||
setConfirmationDetails({
|
||||
success: false,
|
||||
message: err as string,
|
||||
});
|
||||
} finally {
|
||||
await checkOwnership();
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<NymCard title="Unbond" subheader="Unbond a mixnode or gateway" noPadding>
|
||||
{ownership?.hasOwnership ? (
|
||||
<>
|
||||
<Alert
|
||||
severity="info"
|
||||
data-testid="bond-noded"
|
||||
action={
|
||||
<Button
|
||||
data-testid="un-bond"
|
||||
disabled={isLoading}
|
||||
onClick={async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (ownership.vestingPledge) {
|
||||
await vestingUnbond(ownership.nodeType!);
|
||||
} else {
|
||||
await unbond(ownership.nodeType!);
|
||||
}
|
||||
} catch (e) {
|
||||
enqueueSnackbar(`Failed to unbond ${ownership.nodeType}}`, { variant: 'error' });
|
||||
} finally {
|
||||
await getBondDetails();
|
||||
await checkOwnership();
|
||||
await userBalance.fetchBalance();
|
||||
setIsLoading(false);
|
||||
}
|
||||
}}
|
||||
color="inherit"
|
||||
>
|
||||
Unbond
|
||||
</Button>
|
||||
}
|
||||
sx={{ m: 2 }}
|
||||
>
|
||||
{`Looks like you already have a ${ownership.nodeType} bonded.`}
|
||||
</Alert>
|
||||
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Fee feeType="UnbondMixnode" />
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<Alert severity="info" sx={{ m: 3 }} data-testid="no-bond">
|
||||
You do not currently have a bonded node
|
||||
{!ownership.hasOwnership && (
|
||||
<Alert
|
||||
severity="info"
|
||||
sx={{ m: 3 }}
|
||||
data-testid="no-bond"
|
||||
action={
|
||||
<Button color="inherit" onClick={() => navigate('/bond')}>
|
||||
Bond
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
You do not currently have a bonded mixnode or gateway
|
||||
</Alert>
|
||||
)}
|
||||
{isLoading && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
p: 3,
|
||||
pt: 0,
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={48} />
|
||||
</Box>
|
||||
{ownership.hasOwnership && ownership?.nodeType === EnumNodeType.mixnode && (
|
||||
<UnbondMixnode
|
||||
isWithVestingTokens={!!ownership.vestingPledge}
|
||||
onConfirm={handleUnbondMixnode}
|
||||
onError={(err) =>
|
||||
setConfirmationDetails({
|
||||
success: false,
|
||||
message: err as string,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{ownership.hasOwnership && ownership?.nodeType === EnumNodeType.gateway && (
|
||||
<UnbondGateway
|
||||
isWithVestingTokens={!!ownership.vestingPledge}
|
||||
onConfirm={handleUnbondGateway}
|
||||
onError={(err) =>
|
||||
setConfirmationDetails({
|
||||
success: false,
|
||||
message: err as string,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{isLoading && <LoadingModal />}
|
||||
{confirmationDetails && (
|
||||
<ConfirmationModal {...confirmationDetails} onClose={() => setConfirmationDetails(undefined)} />
|
||||
)}
|
||||
</NymCard>
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
// {fee && <ConfirmationModal fee={fee} nodeType={ownership.nodeType} onPrev={resetFeeState} onConfirm={async () => {
|
||||
// setIsLoading(true);
|
||||
// try {
|
||||
// if (ownership.vestingPledge) {
|
||||
// await vestingUnbond(ownership.nodeType!);
|
||||
// } else {
|
||||
// await unbond(ownership.nodeType!);
|
||||
// }
|
||||
// } catch (e) {
|
||||
// enqueueSnackbar(`Failed to unbond ${ownership.nodeType}}`, { variant: 'error' });
|
||||
// } finally {
|
||||
// await getBondDetails();
|
||||
// await checkOwnership();
|
||||
// await userBalance.fetchBalance();
|
||||
// setIsLoading(false);
|
||||
// }
|
||||
// }}/>}
|
||||
// <NymCard title="Unbond" subheader="Unbond a mixnode or gateway" noPadding>
|
||||
// {ownership?.hasOwnership ? (
|
||||
// <>
|
||||
// <Alert
|
||||
// severity="info"
|
||||
// data-testid="bond-noded"
|
||||
// action={
|
||||
// <Button
|
||||
// data-testid="un-bond"
|
||||
// disabled={isLoading}
|
||||
// onClick={async () => {
|
||||
// setIsLoading(true)
|
||||
// if (ownership.vestingPledge) {
|
||||
// await getFee(simulateVestingUnbondMixnode);
|
||||
// } else {
|
||||
// await getFee(unbond, {type: ownership.nodeType})
|
||||
// }
|
||||
// }}
|
||||
// color="inherit"
|
||||
// >
|
||||
// Unbond
|
||||
// </Button>
|
||||
// }
|
||||
// sx={{ m: 2 }}
|
||||
// >
|
||||
// {`Looks like you already have a ${ownership.nodeType} bonded.`}
|
||||
// </Alert>
|
||||
|
||||
// <Box sx={{ p: 3 }}>
|
||||
// <Fee feeType="UnbondMixnode" />
|
||||
// </Box>
|
||||
// </>
|
||||
// ) : (
|
||||
// <Alert severity="info" sx={{ m: 3 }} data-testid="no-bond">
|
||||
// You do not currently have a bonded node
|
||||
// </Alert>
|
||||
// )}
|
||||
// {isLoading && (
|
||||
// <Box
|
||||
// sx={{
|
||||
// display: 'flex',
|
||||
// justifyContent: 'center',
|
||||
// p: 3,
|
||||
// pt: 0,
|
||||
// }}
|
||||
// >
|
||||
// <CircularProgress size={48} />
|
||||
// </Box>
|
||||
// )}
|
||||
// </NymCard>
|
||||
|
||||
@@ -13,18 +13,18 @@ import {
|
||||
} from '@mui/material';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { format } from 'date-fns';
|
||||
import { EnumNodeType, PendingUndelegate, TDelegation } from '@nymproject/types';
|
||||
import { TNodeType, PendingUndelegate, TDelegation } from '@nymproject/types';
|
||||
import { validationSchema } from './validationSchema';
|
||||
import { undelegateFromMixnode, vestingUndelegateFromMixnode } from '../../requests';
|
||||
import { Fee } from '../../components';
|
||||
|
||||
type TFormData = {
|
||||
nodeType: EnumNodeType;
|
||||
nodeType: TNodeType;
|
||||
identity: string;
|
||||
};
|
||||
|
||||
const defaultValues = {
|
||||
nodeType: EnumNodeType.mixnode,
|
||||
nodeType: 'Mixnode' as TNodeType,
|
||||
identity: '',
|
||||
};
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { MajorCurrencyAmount, SendTxResult, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { EnumNodeType, TBondArgs, TBondGatewayArgs, TBondMixNodeArgs } from '../types';
|
||||
import { Fee } from '@nymproject/types/dist/types/rust/Fee';
|
||||
import { EnumNodeType, TBondGatewayArgs, TBondMixNodeArgs } from '../types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const bondGateway = async (args: TBondGatewayArgs) =>
|
||||
invokeWrapper<TransactionExecuteResult>('bond_gateway', args);
|
||||
|
||||
export const unbondGateway = async () => invokeWrapper<TransactionExecuteResult>('unbond_gateway');
|
||||
export const unbondGateway = async (fee?: Fee) => invokeWrapper<TransactionExecuteResult>('unbond_gateway', { fee });
|
||||
|
||||
export const bondMixNode = async (args: TBondMixNodeArgs) =>
|
||||
invokeWrapper<TransactionExecuteResult>('bond_mixnode', args);
|
||||
|
||||
export const unbondMixNode = async () => invokeWrapper<TransactionExecuteResult>('unbond_mixnode');
|
||||
export const unbondMixNode = async (fee?: Fee) => invokeWrapper<TransactionExecuteResult>('unbond_mixnode', { fee });
|
||||
|
||||
export const updateMixnode = async (profitMarginPercent: number) =>
|
||||
invokeWrapper<TransactionExecuteResult>('update_mixnode', { profitMarginPercent });
|
||||
@@ -22,9 +23,3 @@ export const unbond = async (type: EnumNodeType) => {
|
||||
if (type === EnumNodeType.mixnode) return unbondMixNode();
|
||||
return unbondGateway();
|
||||
};
|
||||
|
||||
export const bond = async (args: TBondArgs) => {
|
||||
const { type, ...other } = args;
|
||||
if (type === EnumNodeType.mixnode) return bondMixNode(other as TBondMixNodeArgs);
|
||||
return bondGateway(other as TBondGatewayArgs);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { FeeDetails, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { FeeDetails, MajorCurrencyAmount, Gateway, MixNode } from '@nymproject/types';
|
||||
import { TBondGatewayArgs, TBondMixNodeArgs } from 'src/types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const simulateBondGateway = async (args: any) => invokeWrapper<FeeDetails>('simulate_bond_gateway', args);
|
||||
export const simulateBondGateway = async (args: TBondGatewayArgs) =>
|
||||
invokeWrapper<FeeDetails>('simulate_bond_gateway', args);
|
||||
|
||||
export const simulateUnbondGateway = async (args: any) => invokeWrapper<FeeDetails>('simulate_unbond_gateway', args);
|
||||
|
||||
export const simulateBondMixnode = async (args: any) => invokeWrapper<FeeDetails>('simulate_bond_mixnode', args);
|
||||
export const simulateBondMixnode = async (args: TBondMixNodeArgs) =>
|
||||
invokeWrapper<FeeDetails>('simulate_bond_mixnode', args);
|
||||
|
||||
export const simulateUnbondMixnode = async (args: any) => invokeWrapper<FeeDetails>('simulate_unbond_mixnode', args);
|
||||
|
||||
@@ -32,8 +35,11 @@ export const simulateVestingCompoundDelgatorReward = async (identity: string) =>
|
||||
export const simulateVestingUndelegateFromMixnode = async (args: any) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_undelegate_from_mixnode', args);
|
||||
|
||||
export const simulateVestingBondGateway = async (args: any) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_bond_gateway', args);
|
||||
export const simulateVestingBondGateway = async (args: {
|
||||
gateway: Gateway;
|
||||
pledge: MajorCurrencyAmount;
|
||||
ownerSignature: string;
|
||||
}) => invokeWrapper<FeeDetails>('simulate_vesting_bond_gateway', args);
|
||||
|
||||
export const simulateVestingUnbondGateway = async (args: any) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_unbond_gateway', args);
|
||||
@@ -41,11 +47,13 @@ export const simulateVestingUnbondGateway = async (args: any) =>
|
||||
export const simulateVestingDelegateToMixnode = async (args: { identity: string }) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_delegate_to_mixnode', args);
|
||||
|
||||
export const simulateVestingBondMixnode = async (args: any) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_bond_mixnode', args);
|
||||
export const simulateVestingBondMixnode = async (args: {
|
||||
mixnode: MixNode;
|
||||
pledge: MajorCurrencyAmount;
|
||||
ownerSignature: string;
|
||||
}) => invokeWrapper<FeeDetails>('simulate_vesting_bond_mixnode', args);
|
||||
|
||||
export const simulateVestingUnbondMixnode = async (args: any) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_unbond_mixnode', args);
|
||||
export const simulateVestingUnbondMixnode = async () => invokeWrapper<FeeDetails>('simulate_vesting_unbond_mixnode');
|
||||
|
||||
export const simulateVestingUpdateMixnode = async (args: any) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_update_mixnode', args);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
EnumNodeType,
|
||||
TNodeType,
|
||||
FeeDetails,
|
||||
Gateway,
|
||||
MajorCurrencyAmount,
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
TransactionExecuteResult,
|
||||
VestingAccountInfo,
|
||||
} from '@nymproject/types';
|
||||
import { Fee } from '@nymproject/types/dist/types/rust/Fee';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
import { TBondGatewayArgs, TBondMixNodeArgs } from '../types';
|
||||
|
||||
export const getLockedCoins = async (): Promise<MajorCurrencyAmount> =>
|
||||
invokeWrapper<MajorCurrencyAmount>('locked_coins');
|
||||
@@ -43,7 +43,8 @@ export const vestingBondGateway = async ({
|
||||
ownerSignature: string;
|
||||
}) => invokeWrapper<TransactionExecuteResult>('vesting_bond_gateway', { gateway, ownerSignature, pledge });
|
||||
|
||||
export const vestingUnbondGateway = async () => invokeWrapper<TransactionExecuteResult>('vesting_unbond_gateway');
|
||||
export const vestingUnbondGateway = async (fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_unbond_gateway', { fee });
|
||||
|
||||
export const vestingBondMixNode = async ({
|
||||
mixnode,
|
||||
@@ -55,7 +56,8 @@ export const vestingBondMixNode = async ({
|
||||
ownerSignature: string;
|
||||
}) => invokeWrapper<TransactionExecuteResult>('vesting_bond_mixnode', { mixnode, ownerSignature, pledge });
|
||||
|
||||
export const vestingUnbondMixnode = async () => invokeWrapper<TransactionExecuteResult>('vesting_unbond_mixnode');
|
||||
export const vestingUnbondMixnode = async (fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_unbond_mixnode', { fee });
|
||||
|
||||
export const withdrawVestedCoins = async (amount: MajorCurrencyAmount) =>
|
||||
invokeWrapper<TransactionExecuteResult>('withdraw_vested_coins', { amount });
|
||||
@@ -84,7 +86,7 @@ export const getVestingPledgeInfo = async ({
|
||||
type,
|
||||
}: {
|
||||
address?: string;
|
||||
type: EnumNodeType;
|
||||
type: TNodeType;
|
||||
}): Promise<PledgeData | undefined> => {
|
||||
try {
|
||||
return await invokeWrapper<PledgeData>(`vesting_get_${type}_pledge`, { address });
|
||||
@@ -96,17 +98,11 @@ export const getVestingPledgeInfo = async ({
|
||||
export const vestingDelegatedFree = async (vestingAccountAddress: string) =>
|
||||
invokeWrapper<MajorCurrencyAmount>('delegated_free', { vestingAccountAddress });
|
||||
|
||||
export const vestingUnbond = async (type: EnumNodeType) => {
|
||||
if (type === EnumNodeType.mixnode) return vestingUnbondMixnode();
|
||||
export const vestingUnbond = async (type: TNodeType) => {
|
||||
if (type === 'mixnode') return vestingUnbondMixnode();
|
||||
return vestingUnbondGateway();
|
||||
};
|
||||
|
||||
export const vestingBond = async (args: { type: EnumNodeType } & (TBondMixNodeArgs | TBondGatewayArgs)) => {
|
||||
const { type, ...other } = args;
|
||||
if (type === EnumNodeType.mixnode) return vestingBondMixNode(other as TBondMixNodeArgs);
|
||||
return vestingBondGateway(other as TBondGatewayArgs);
|
||||
};
|
||||
|
||||
export const vestingClaimOperatorRewards = async () =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_claim_operator_reward');
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Gateway, MajorCurrencyAmount, MixNode, PledgeData } from '@nymproject/types';
|
||||
import { Fee } from '@nymproject/types/dist/types/rust/Fee';
|
||||
|
||||
export enum EnumNodeType {
|
||||
mixnode = 'mixnode',
|
||||
@@ -28,16 +29,16 @@ export type TBondGatewayArgs = {
|
||||
gateway: Gateway;
|
||||
pledge: MajorCurrencyAmount;
|
||||
ownerSignature: string;
|
||||
fee?: Fee;
|
||||
};
|
||||
|
||||
export type TBondMixNodeArgs = {
|
||||
mixnode: MixNode;
|
||||
pledge: MajorCurrencyAmount;
|
||||
ownerSignature: string;
|
||||
fee?: Fee;
|
||||
};
|
||||
|
||||
export type TBondArgs = { type: EnumNodeType } & (TBondGatewayArgs | TBondMixNodeArgs);
|
||||
|
||||
export type TDelegateArgs = {
|
||||
identity: string;
|
||||
amount: MajorCurrencyAmount;
|
||||
|
||||
@@ -35,9 +35,9 @@
|
||||
chalk "^4.0.0"
|
||||
|
||||
"@sindresorhus/is@^4.0.0":
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.2.0.tgz#667bfc6186ae7c9e0b45a08960c551437176e1ca"
|
||||
integrity sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
|
||||
integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==
|
||||
|
||||
"@szmarczak/http-timer@^4.0.5":
|
||||
version "4.0.6"
|
||||
@@ -115,10 +115,15 @@
|
||||
dependencies:
|
||||
"@types/istanbul-lib-report" "*"
|
||||
|
||||
"@types/json-buffer@~3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-buffer/-/json-buffer-3.0.0.tgz#85c1ff0f0948fc159810d4b5be35bf8c20875f64"
|
||||
integrity sha512-3YP80IxxFJB4b5tYC2SUPwkg0XQLiu0nWvhRgEatgjf+29IcWO9X1k8xRv5DGssJ/lCrjYTjQPcobJr2yWIVuQ==
|
||||
|
||||
"@types/keyv@*":
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.3.tgz#1c9aae32872ec1f20dcdaee89a9f3ba88f465e41"
|
||||
integrity sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6"
|
||||
integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
@@ -153,7 +158,12 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.0.0.tgz#3205bcd15ada9bc681ac20bef64e9e6df88fd297"
|
||||
integrity sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA==
|
||||
|
||||
"@types/node@*", "@types/node@^16.11.0", "@types/node@^16.11.1":
|
||||
"@types/node@*":
|
||||
version "18.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.0.tgz#67c7b724e1bcdd7a8821ce0d5ee184d3b4dd525a"
|
||||
integrity sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==
|
||||
|
||||
"@types/node@^16.11.0", "@types/node@^16.11.1":
|
||||
version "16.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.1.tgz#2e50a649a50fc403433a14f829eface1a3443e97"
|
||||
integrity sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA==
|
||||
@@ -647,7 +657,7 @@ cacheable-lookup@^5.0.3:
|
||||
resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005"
|
||||
integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==
|
||||
|
||||
cacheable-request@^7.0.1:
|
||||
cacheable-request@^7.0.2:
|
||||
version "7.0.2"
|
||||
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27"
|
||||
integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==
|
||||
@@ -770,7 +780,7 @@ cliui@^7.0.2:
|
||||
clone-response@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
|
||||
integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=
|
||||
integrity sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==
|
||||
dependencies:
|
||||
mimic-response "^1.0.0"
|
||||
|
||||
@@ -803,6 +813,14 @@ color-name@~1.1.4:
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
compress-brotli@^1.3.8:
|
||||
version "1.3.8"
|
||||
resolved "https://registry.yarnpkg.com/compress-brotli/-/compress-brotli-1.3.8.tgz#0c0a60c97a989145314ec381e84e26682e7b38db"
|
||||
integrity sha512-lVcQsjhxhIXsuupfy9fmZUFtAIdBmXA7EGY6GBdgZ++qkM9zG4YFT8iU7FoBxzryNDMOpD1HIFHUSX4D87oqhQ==
|
||||
dependencies:
|
||||
"@types/json-buffer" "~3.0.0"
|
||||
json-buffer "~3.0.1"
|
||||
|
||||
compress-commons@^4.1.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d"
|
||||
@@ -1228,16 +1246,16 @@ globule@^1.0.0:
|
||||
minimatch "~3.0.2"
|
||||
|
||||
got@^11.0.2, got@^11.8.1:
|
||||
version "11.8.2"
|
||||
resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599"
|
||||
integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==
|
||||
version "11.8.5"
|
||||
resolved "https://registry.yarnpkg.com/got/-/got-11.8.5.tgz#ce77d045136de56e8f024bebb82ea349bc730046"
|
||||
integrity sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==
|
||||
dependencies:
|
||||
"@sindresorhus/is" "^4.0.0"
|
||||
"@szmarczak/http-timer" "^4.0.5"
|
||||
"@types/cacheable-request" "^6.0.1"
|
||||
"@types/responselike" "^1.0.0"
|
||||
cacheable-lookup "^5.0.3"
|
||||
cacheable-request "^7.0.1"
|
||||
cacheable-request "^7.0.2"
|
||||
decompress-response "^6.0.0"
|
||||
http2-wrapper "^1.0.0-beta.5.2"
|
||||
lowercase-keys "^2.0.0"
|
||||
@@ -1521,7 +1539,7 @@ js2xmlparser@^3.0.0:
|
||||
dependencies:
|
||||
xmlcreate "^1.0.1"
|
||||
|
||||
json-buffer@3.0.1:
|
||||
json-buffer@3.0.1, json-buffer@~3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
|
||||
integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
|
||||
@@ -1543,10 +1561,11 @@ jsonfile@^6.0.1:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
keyv@^4.0.0:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254"
|
||||
integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.3.2.tgz#e839df676a0c7ee594c8835e7c1c83742558e5c2"
|
||||
integrity sha512-kn8WmodVBe12lmHpA6W8OY7SNh6wVR+Z+wZESF4iF5FCazaVXGWOtnbnvX0tMQ1bO+/TmOD9LziuYMvrIIs0xw==
|
||||
dependencies:
|
||||
compress-brotli "^1.3.8"
|
||||
json-buffer "3.0.1"
|
||||
|
||||
ky@^0.28.5:
|
||||
@@ -1854,7 +1873,7 @@ object-inspect@^1.10.3:
|
||||
once@^1.3.0, once@^1.3.1, once@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
||||
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
|
||||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
@@ -2585,7 +2604,7 @@ wrap-ansi@^7.0.0:
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
|
||||
|
||||
ws@7.4.6:
|
||||
version "7.4.6"
|
||||
|
||||
+14
-14
@@ -1096,7 +1096,7 @@
|
||||
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
|
||||
integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78=
|
||||
integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==
|
||||
|
||||
"@protobufjs/base64@^1.1.2":
|
||||
version "1.1.2"
|
||||
@@ -1111,12 +1111,12 @@
|
||||
"@protobufjs/eventemitter@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
|
||||
integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A=
|
||||
integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==
|
||||
|
||||
"@protobufjs/fetch@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
|
||||
integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=
|
||||
integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==
|
||||
dependencies:
|
||||
"@protobufjs/aspromise" "^1.1.1"
|
||||
"@protobufjs/inquire" "^1.1.0"
|
||||
@@ -1124,27 +1124,27 @@
|
||||
"@protobufjs/float@^1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
|
||||
integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=
|
||||
integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==
|
||||
|
||||
"@protobufjs/inquire@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
|
||||
integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=
|
||||
integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==
|
||||
|
||||
"@protobufjs/path@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
|
||||
integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=
|
||||
integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==
|
||||
|
||||
"@protobufjs/pool@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
|
||||
integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=
|
||||
integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==
|
||||
|
||||
"@protobufjs/utf8@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
|
||||
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
|
||||
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
|
||||
|
||||
"@swc/helpers@^0.3.6":
|
||||
version "0.3.8"
|
||||
@@ -1167,9 +1167,9 @@
|
||||
integrity sha512-0fDwydE2clKe9MNfvXHBHF9WEahRuj+msTuQqOmAApNORFvhMYZKNGGJdCzuhheVjMps/ti0Ak/iJPACMaevvw==
|
||||
|
||||
"@types/long@^4.0.1":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
|
||||
integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
|
||||
integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==
|
||||
|
||||
"@types/node@11.11.6":
|
||||
version "11.11.6"
|
||||
@@ -3065,9 +3065,9 @@ protobufjs@^6.8.8:
|
||||
long "^4.0.0"
|
||||
|
||||
protobufjs@~6.10.2:
|
||||
version "6.10.2"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.2.tgz#b9cb6bd8ec8f87514592ba3fdfd28e93f33a469b"
|
||||
integrity sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==
|
||||
version "6.10.3"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.3.tgz#11ed1dd02acbfcb330becf1611461d4b407f9eef"
|
||||
integrity sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==
|
||||
dependencies:
|
||||
"@protobufjs/aspromise" "^1.1.2"
|
||||
"@protobufjs/base64" "^1.1.2"
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { MixNode, MajorCurrencyAmount } from './rust';
|
||||
|
||||
export enum EnumNodeType {
|
||||
mixnode = 'mixnode',
|
||||
gateway = 'gateway',
|
||||
}
|
||||
export type TNodeType = 'mixnode' | 'gateway';
|
||||
|
||||
export type TNodeOwnership = {
|
||||
hasOwnership: boolean;
|
||||
nodeType?: EnumNodeType;
|
||||
nodeType?: TNodeType;
|
||||
};
|
||||
|
||||
export type TDelegation = {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import type { CosmosFee } from "./CosmosFee";
|
||||
import type { CosmosFee } from './CosmosFee';
|
||||
|
||||
export type Fee = { Manual: CosmosFee } | { Auto: number | null };
|
||||
export type Fee = { Manual: CosmosFee } | { Auto: number | null };
|
||||
|
||||
@@ -3368,7 +3368,7 @@
|
||||
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
|
||||
integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78=
|
||||
integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==
|
||||
|
||||
"@protobufjs/base64@^1.1.2":
|
||||
version "1.1.2"
|
||||
@@ -3383,12 +3383,12 @@
|
||||
"@protobufjs/eventemitter@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
|
||||
integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A=
|
||||
integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==
|
||||
|
||||
"@protobufjs/fetch@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
|
||||
integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=
|
||||
integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==
|
||||
dependencies:
|
||||
"@protobufjs/aspromise" "^1.1.1"
|
||||
"@protobufjs/inquire" "^1.1.0"
|
||||
@@ -3396,27 +3396,27 @@
|
||||
"@protobufjs/float@^1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
|
||||
integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=
|
||||
integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==
|
||||
|
||||
"@protobufjs/inquire@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
|
||||
integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=
|
||||
integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==
|
||||
|
||||
"@protobufjs/path@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
|
||||
integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=
|
||||
integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==
|
||||
|
||||
"@protobufjs/pool@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
|
||||
integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=
|
||||
integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==
|
||||
|
||||
"@protobufjs/utf8@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
|
||||
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
|
||||
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
|
||||
|
||||
"@sinonjs/commons@^1.7.0":
|
||||
version "1.8.3"
|
||||
@@ -4996,9 +4996,9 @@
|
||||
integrity sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w==
|
||||
|
||||
"@types/long@^4.0.1":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
|
||||
integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
|
||||
integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==
|
||||
|
||||
"@types/mdast@^3.0.0":
|
||||
version "3.0.10"
|
||||
@@ -5045,16 +5045,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.52.tgz#03c13be70b9031baaed79481c0c0cfb0045e53f7"
|
||||
integrity sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==
|
||||
|
||||
"@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0":
|
||||
"@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0", "@types/node@^16.7.13":
|
||||
version "16.11.39"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.39.tgz#07223cd2bc332ad9d92135e3a522eebdee3b060e"
|
||||
integrity sha512-K0MsdV42vPwm9L6UwhIxMAOmcvH/1OoVkZyCgEtVu4Wx7sElGloy/W7kMBNe/oJ7V/jW9BVt1F6RahH6e7tPXw==
|
||||
|
||||
"@types/node@^16.7.13":
|
||||
version "16.11.26"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.26.tgz#63d204d136c9916fb4dcd1b50f9740fe86884e47"
|
||||
integrity sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==
|
||||
|
||||
"@types/normalize-package-data@^2.4.0":
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
|
||||
@@ -14983,9 +14978,9 @@ protobufjs@^6.8.8:
|
||||
long "^4.0.0"
|
||||
|
||||
protobufjs@~6.10.2:
|
||||
version "6.10.2"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.2.tgz#b9cb6bd8ec8f87514592ba3fdfd28e93f33a469b"
|
||||
integrity sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==
|
||||
version "6.10.3"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.3.tgz#11ed1dd02acbfcb330becf1611461d4b407f9eef"
|
||||
integrity sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==
|
||||
dependencies:
|
||||
"@protobufjs/aspromise" "^1.1.2"
|
||||
"@protobufjs/base64" "^1.1.2"
|
||||
|
||||
Reference in New Issue
Block a user