Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 165e7d8b27 | |||
| 3c97d0d16b | |||
| bc55c10e19 | |||
| a925c39642 | |||
| d23fb366e4 | |||
| 3d500c25c5 | |||
| 9348722b84 | |||
| 726a406797 | |||
| 4652d65874 | |||
| a4ca94ccef | |||
| 24839770ff | |||
| 7ac3ec3598 | |||
| 77ae71eba4 | |||
| d4b836277e | |||
| b92ee84874 | |||
| 2eb0ce381a | |||
| 9f42f0152b | |||
| 5217edcca3 | |||
| e306effdac | |||
| dc2b1c6d2a | |||
| 4232801e80 | |||
| 96df3ad4ce | |||
| d614a2b81b | |||
| d27245e184 | |||
| 5dbfcadfdb | |||
| 035dada0e0 | |||
| 1d867156e3 | |||
| ed9be47ec4 | |||
| 3aa2e6c54d | |||
| eb96fc72b9 | |||
| 59cec6f03c | |||
| c0a0d89a90 | |||
| 362e7f2fea | |||
| eeba17a01f | |||
| 3bc7f281b4 | |||
| 5a89e894a9 | |||
| 8dbddb7b7e | |||
| b62c969a7c | |||
| 5d10e62450 | |||
| 21e636616d | |||
| 9881a94757 | |||
| 76b07d487b | |||
| f04fc452dc | |||
| be90d03129 | |||
| 0a3e42700c | |||
| 55d554701c | |||
| 19c4769260 | |||
| 71aadc8e1b | |||
| 95340b5817 | |||
| 12751665bb | |||
| 01b86bcc0d | |||
| c6ce8caaf7 | |||
| 265713b9d2 | |||
| c9af4721f3 | |||
| 8c0ab7c697 | |||
| 92b220ca4b | |||
| c218cba96c | |||
| c958975fff |
@@ -0,0 +1,112 @@
|
||||
name: Build and upload binaries to CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'clients/**'
|
||||
- 'common/**'
|
||||
- 'contracts/**'
|
||||
- 'explorer-api/**'
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'mixnode/**'
|
||||
- 'sdk/rust/nym-sdk/**'
|
||||
- 'service-providers/**'
|
||||
- 'nym-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'tools/nym-cli/**'
|
||||
- 'tools/ts-rs-cli/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'clients/**'
|
||||
- 'common/**'
|
||||
- 'contracts/**'
|
||||
- 'explorer-api/**'
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'mixnode/**'
|
||||
- 'sdk/rust/nym-sdk/**'
|
||||
- 'service-providers/**'
|
||||
- 'nym-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'tools/nym-cli/**'
|
||||
- 'tools/ts-rs-cli/**'
|
||||
|
||||
env:
|
||||
NETWORK: mainnet
|
||||
|
||||
jobs:
|
||||
publish-nym:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-20.04]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare build output directory
|
||||
shell: bash
|
||||
env:
|
||||
OUTPUT_DIR: ci-builds/${{ github.ref_name }}
|
||||
run: |
|
||||
rm -rf ci-builds || true
|
||||
mkdir -p $OUTPUT_DIR
|
||||
echo $OUTPUT_DIR
|
||||
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
|
||||
continue-on-error: true
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --release
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Build release contracts
|
||||
run: make wasm
|
||||
|
||||
- name: Prepare build output
|
||||
shell: bash
|
||||
env:
|
||||
OUTPUT_DIR: ci-builds/${{ github.ref_name }}
|
||||
run: |
|
||||
cp target/release/nym-client $OUTPUT_DIR
|
||||
cp target/release/nym-gateway $OUTPUT_DIR
|
||||
cp target/release/nym-mixnode $OUTPUT_DIR
|
||||
cp target/release/nym-socks5-client $OUTPUT_DIR
|
||||
cp target/release/nym-api $OUTPUT_DIR
|
||||
cp target/release/nym-network-requester $OUTPUT_DIR
|
||||
cp target/release/nym-network-statistics $OUTPUT_DIR
|
||||
cp target/release/nym-cli $OUTPUT_DIR
|
||||
|
||||
cp contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm $OUTPUT_DIR
|
||||
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-avzr"
|
||||
SOURCE: "ci-builds/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
+30
-8
@@ -4,18 +4,40 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
# [Unreleased]
|
||||
|
||||
# [v1.1.9] (2023-02-07)
|
||||
|
||||
### Added
|
||||
- Remove Coconut feature flag ([#2793])
|
||||
- Separate `nym-api` endpoints with values of "total-supply" and "circulating-supply" in `nym` ([#2964])
|
||||
|
||||
### Changed
|
||||
- native-client: is now capable of listening for requests on sockets different than `127.0.0.1` ([#2912]). This can be specified via `--host` flag during `init` or `run`. Alternatively a custom `host` can be set in `config.toml` file under `socket` section.
|
||||
- mixnode, gateway: fix unexpected shutdown on corrupted connection ([#2963])
|
||||
|
||||
[#2793]: https://github.com/nymtech/nym/issues/2793
|
||||
[#2912]: https://github.com/nymtech/nym/issues/2912
|
||||
[#2964]: https://github.com/nymtech/nym/issues/2964
|
||||
[#2963]: https://github.com/nymtech/nym/issues/3017
|
||||
|
||||
# [v1.1.8] (2023-01-31)
|
||||
|
||||
### Added
|
||||
|
||||
- dkg rerun from scratch and dkg-specific epochs ([#2839])
|
||||
- nym-sdk: add support for surb storage ([#2870])
|
||||
- nym-sdk: enable reply-SURBs by default ([#2874])
|
||||
- remove coconut feature and unify builds ([#2890])
|
||||
- Rust SDK - Support SURBS (anonymous send + storage) ([#2754])
|
||||
- dkg rerun from scratch and dkg-specific epochs ([#2810])
|
||||
- Rename `'initial_supply'` field to `'total_supply'` in the circulating supply endpoint ([#2931])
|
||||
- Circulating supply api endpoint (read the note inside before testing/deploying) ([#1902])
|
||||
|
||||
### Changed
|
||||
|
||||
- nym-api: an `--id` flag is now always explicitly required ([#2873])
|
||||
|
||||
[#2754]: https://github.com/nymtech/nym/issues/2754
|
||||
[#2810]: https://github.com/nymtech/nym/issues/2810
|
||||
[#2931]: https://github.com/nymtech/nym/issues/2931
|
||||
[#1902]: https://github.com/nymtech/nym/issues/1902
|
||||
[#2873]: https://github.com/nymtech/nym/issues/2873
|
||||
|
||||
[#2839]: https://github.com/nymtech/nym/pull/2839
|
||||
[#2870]: https://github.com/nymtech/nym/pull/2870
|
||||
[#2874]: https://github.com/nymtech/nym/pull/2874
|
||||
[#2890]: https://github.com/nymtech/nym/pull/2890
|
||||
|
||||
# [v1.1.7] (2023-01-24)
|
||||
|
||||
|
||||
Generated
+11
-10
@@ -701,7 +701,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "client-core"
|
||||
version = "1.1.7"
|
||||
version = "1.1.8"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"client-connections",
|
||||
@@ -1891,7 +1891,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "1.1.7"
|
||||
version = "1.1.8"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 4.1.3",
|
||||
@@ -3314,6 +3314,7 @@ dependencies = [
|
||||
"cw4",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3437,7 +3438,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.7"
|
||||
version = "1.1.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3535,7 +3536,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.7"
|
||||
version = "1.1.8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.13.1",
|
||||
@@ -3593,7 +3594,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.7"
|
||||
version = "1.1.8"
|
||||
dependencies = [
|
||||
"build-information",
|
||||
"clap 4.1.3",
|
||||
@@ -3633,7 +3634,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-gateway"
|
||||
version = "1.1.7"
|
||||
version = "1.1.8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3685,7 +3686,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnode"
|
||||
version = "1.1.8"
|
||||
version = "1.1.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"atty",
|
||||
@@ -3730,7 +3731,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.7"
|
||||
version = "1.1.8"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"clap 4.1.3",
|
||||
@@ -3762,7 +3763,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-statistics"
|
||||
version = "1.1.7"
|
||||
version = "1.1.8"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"log",
|
||||
@@ -3819,7 +3820,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.7"
|
||||
version = "1.1.8"
|
||||
dependencies = [
|
||||
"build-information",
|
||||
"clap 4.1.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "client-core"
|
||||
version = "1.1.7"
|
||||
version = "1.1.9"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.66"
|
||||
|
||||
@@ -546,35 +546,35 @@ impl<T: NymConfig> Default for Client<T> {
|
||||
|
||||
impl<T: NymConfig> Client<T> {
|
||||
fn default_private_identity_key_file(id: &str) -> PathBuf {
|
||||
T::default_data_directory(Some(id)).join("private_identity.pem")
|
||||
T::default_data_directory(id).join("private_identity.pem")
|
||||
}
|
||||
|
||||
fn default_public_identity_key_file(id: &str) -> PathBuf {
|
||||
T::default_data_directory(Some(id)).join("public_identity.pem")
|
||||
T::default_data_directory(id).join("public_identity.pem")
|
||||
}
|
||||
|
||||
fn default_private_encryption_key_file(id: &str) -> PathBuf {
|
||||
T::default_data_directory(Some(id)).join("private_encryption.pem")
|
||||
T::default_data_directory(id).join("private_encryption.pem")
|
||||
}
|
||||
|
||||
fn default_public_encryption_key_file(id: &str) -> PathBuf {
|
||||
T::default_data_directory(Some(id)).join("public_encryption.pem")
|
||||
T::default_data_directory(id).join("public_encryption.pem")
|
||||
}
|
||||
|
||||
fn default_gateway_shared_key_file(id: &str) -> PathBuf {
|
||||
T::default_data_directory(Some(id)).join("gateway_shared.pem")
|
||||
T::default_data_directory(id).join("gateway_shared.pem")
|
||||
}
|
||||
|
||||
fn default_ack_key_file(id: &str) -> PathBuf {
|
||||
T::default_data_directory(Some(id)).join("ack_key.pem")
|
||||
T::default_data_directory(id).join("ack_key.pem")
|
||||
}
|
||||
|
||||
fn default_reply_surb_database_path(id: &str) -> PathBuf {
|
||||
T::default_data_directory(Some(id)).join("persistent_reply_store.sqlite")
|
||||
T::default_data_directory(id).join("persistent_reply_store.sqlite")
|
||||
}
|
||||
|
||||
fn default_database_path(id: &str) -> PathBuf {
|
||||
T::default_data_directory(Some(id)).join(DB_FILE_NAME)
|
||||
T::default_data_directory(id).join(DB_FILE_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ pub fn load_existing_gateway_config<T>(id: &str) -> Result<GatewayEndpointConfig
|
||||
where
|
||||
T: NymConfig + ClientCoreConfigTrait,
|
||||
{
|
||||
T::load_from_file(Some(id))
|
||||
T::load_from_file(id)
|
||||
.map(|existing_config| existing_config.get_gateway_endpoint().clone())
|
||||
.map_err(|err| {
|
||||
log::error!(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.7"
|
||||
version = "1.1.9"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -9,6 +9,7 @@ use config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
|
||||
use config::{NymConfig, OptionalSet};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -104,6 +105,11 @@ impl Config {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_host(mut self, host: IpAddr) -> Self {
|
||||
self.socket.host = host;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_port(mut self, port: u16) -> Self {
|
||||
self.socket.listening_port = port;
|
||||
self
|
||||
@@ -130,6 +136,10 @@ impl Config {
|
||||
self.socket.socket_type
|
||||
}
|
||||
|
||||
pub fn get_listening_ip(&self) -> IpAddr {
|
||||
self.socket.host
|
||||
}
|
||||
|
||||
pub fn get_listening_port(&self) -> u16 {
|
||||
self.socket.listening_port
|
||||
}
|
||||
@@ -180,9 +190,10 @@ impl Config {
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct Socket {
|
||||
socket_type: SocketType,
|
||||
host: IpAddr,
|
||||
listening_port: u16,
|
||||
}
|
||||
|
||||
@@ -190,6 +201,7 @@ impl Default for Socket {
|
||||
fn default() -> Self {
|
||||
Socket {
|
||||
socket_type: SocketType::WebSocket,
|
||||
host: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
|
||||
listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,9 @@ socket_type = '{{ socket.socket_type }}'
|
||||
# will be listening for incoming requests
|
||||
listening_port = {{ socket.listening_port }}
|
||||
|
||||
# if applicable (for the case of 'WebSocket'), the ip address on which the client
|
||||
# will be listening for incoming requests
|
||||
host = '{{ socket.host }}'
|
||||
|
||||
##### logging configuration options #####
|
||||
|
||||
|
||||
@@ -102,7 +102,8 @@ impl SocketClient {
|
||||
reply_controller_sender,
|
||||
);
|
||||
|
||||
websocket::Listener::new(config.get_listening_port()).start(websocket_handler, shutdown);
|
||||
websocket::Listener::new(config.get_listening_ip(), config.get_listening_port())
|
||||
.start(websocket_handler, shutdown);
|
||||
}
|
||||
|
||||
/// blocking version of `start_socket` method. Will run forever (or until SIGINT is sent)
|
||||
|
||||
@@ -12,6 +12,7 @@ use crypto::asymmetric::identity;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Display;
|
||||
use std::net::IpAddr;
|
||||
use tap::TapFallible;
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
@@ -30,7 +31,7 @@ pub(crate) struct Init {
|
||||
force_register_gateway: bool,
|
||||
|
||||
/// Comma separated list of rest endpoints of the nyxd validators
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',')]
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',', hide = true)]
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the API validators
|
||||
@@ -46,6 +47,10 @@ pub(crate) struct Init {
|
||||
#[clap(short, long)]
|
||||
port: Option<u16>,
|
||||
|
||||
/// Ip for the socket (if applicable) to listen for requests.
|
||||
#[clap(long)]
|
||||
host: Option<IpAddr>,
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[clap(long, hide = true)]
|
||||
@@ -57,7 +62,7 @@ pub(crate) struct Init {
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[clap(long)]
|
||||
#[clap(long, hide = true)]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
|
||||
/// Save a summary of the initialization to a json file
|
||||
@@ -71,6 +76,7 @@ impl From<Init> for OverrideConfig {
|
||||
nym_apis: init_config.nym_apis,
|
||||
disable_socket: init_config.disable_socket,
|
||||
port: init_config.port,
|
||||
host: init_config.host,
|
||||
fastmode: init_config.fastmode,
|
||||
no_cover: init_config.no_cover,
|
||||
|
||||
@@ -108,7 +114,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
|
||||
|
||||
let id = &args.id;
|
||||
|
||||
let already_init = Config::default_config_file_path(Some(id)).exists();
|
||||
let already_init = Config::default_config_file_path(id).exists();
|
||||
if already_init {
|
||||
println!("Client \"{id}\" was already initialised before");
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use completions::{fig_generate, ArgShell};
|
||||
use config::OptionalSet;
|
||||
use lazy_static::lazy_static;
|
||||
use std::error::Error;
|
||||
use std::net::IpAddr;
|
||||
|
||||
pub(crate) mod init;
|
||||
pub(crate) mod run;
|
||||
@@ -56,6 +57,7 @@ pub(crate) struct OverrideConfig {
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
disable_socket: Option<bool>,
|
||||
port: Option<u16>,
|
||||
host: Option<IpAddr>,
|
||||
fastmode: bool,
|
||||
no_cover: bool,
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
@@ -81,6 +83,7 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
|
||||
.with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode)
|
||||
.with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover)
|
||||
.with_optional(Config::with_port, args.port)
|
||||
.with_optional(Config::with_host, args.host)
|
||||
.with_optional_custom_env_ext(
|
||||
BaseConfig::with_custom_nym_apis,
|
||||
args.nym_apis,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::error::Error;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use crate::{
|
||||
client::{config::Config, SocketClient},
|
||||
@@ -22,7 +23,7 @@ pub(crate) struct Run {
|
||||
id: String,
|
||||
|
||||
/// Comma separated list of rest endpoints of the nyxd validators
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',')]
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',', hide = true)]
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the API validators
|
||||
@@ -43,6 +44,10 @@ pub(crate) struct Run {
|
||||
#[clap(short, long)]
|
||||
port: Option<u16>,
|
||||
|
||||
/// Ip for the socket (if applicable) to listen for requests.
|
||||
#[clap(long)]
|
||||
host: Option<IpAddr>,
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[clap(long, hide = true)]
|
||||
@@ -54,7 +59,7 @@ pub(crate) struct Run {
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[clap(long)]
|
||||
#[clap(long, hide = true)]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
}
|
||||
|
||||
@@ -64,6 +69,7 @@ impl From<Run> for OverrideConfig {
|
||||
nym_apis: run_config.nym_apis,
|
||||
disable_socket: run_config.disable_socket,
|
||||
port: run_config.port,
|
||||
host: run_config.host,
|
||||
fastmode: run_config.fastmode,
|
||||
no_cover: run_config.no_cover,
|
||||
nyxd_urls: run_config.nyxd_urls,
|
||||
@@ -94,7 +100,7 @@ fn version_check(cfg: &Config) -> bool {
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let id = &args.id;
|
||||
|
||||
let mut config = match Config::load_from_file(Some(id)) {
|
||||
let mut config = match Config::load_from_file(id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id);
|
||||
|
||||
@@ -131,7 +131,7 @@ pub(crate) fn execute(args: &Upgrade) {
|
||||
|
||||
let id = &args.id;
|
||||
|
||||
let existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| {
|
||||
let existing_config = Config::load_from_file(id).unwrap_or_else(|err| {
|
||||
eprintln!("failed to load existing config file! - {err}");
|
||||
process::exit(1)
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use super::handler::HandlerBuilder;
|
||||
use log::*;
|
||||
use std::net::IpAddr;
|
||||
use std::{net::SocketAddr, process, sync::Arc};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::{sync::Notify, task::JoinHandle};
|
||||
@@ -24,10 +25,9 @@ pub(crate) struct Listener {
|
||||
}
|
||||
|
||||
impl Listener {
|
||||
pub(crate) fn new(port: u16) -> Self {
|
||||
pub(crate) fn new(host: IpAddr, port: u16) -> Self {
|
||||
Listener {
|
||||
// unless we find compelling reason not to, just listen on local only
|
||||
address: SocketAddr::new("127.0.0.1".parse().unwrap(), port),
|
||||
address: SocketAddr::new(host, port),
|
||||
state: State::AwaitingConnection,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.7"
|
||||
version = "1.1.9"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -43,7 +43,7 @@ pub(crate) struct Init {
|
||||
force_register_gateway: bool,
|
||||
|
||||
/// Comma separated list of rest endpoints of the nyxd validators
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',')]
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',', hide = true)]
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the API validators
|
||||
@@ -66,7 +66,7 @@ pub(crate) struct Init {
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[clap(long)]
|
||||
#[clap(long, hide = true)]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
|
||||
/// Save a summary of the initialization to a json file
|
||||
@@ -117,7 +117,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
|
||||
let id = &args.id;
|
||||
let provider_address = &args.provider;
|
||||
|
||||
let already_init = Config::default_config_file_path(Some(id)).exists();
|
||||
let already_init = Config::default_config_file_path(id).exists();
|
||||
if already_init {
|
||||
println!("SOCKS5 client \"{id}\" was already initialised before");
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ pub(crate) struct Run {
|
||||
gateway: Option<identity::PublicKey>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the nyxd validators
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',')]
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',', hide = true)]
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the Nym APIs
|
||||
@@ -65,7 +65,7 @@ pub(crate) struct Run {
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[clap(long)]
|
||||
#[clap(long, hide = true)]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ fn version_check(cfg: &Config) -> bool {
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let id = &args.id;
|
||||
|
||||
let mut config = match Config::load_from_file(Some(id)) {
|
||||
let mut config = match Config::load_from_file(id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id);
|
||||
|
||||
@@ -144,7 +144,7 @@ pub(crate) fn execute(args: &Upgrade) {
|
||||
|
||||
let id = &args.id;
|
||||
|
||||
let existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| {
|
||||
let existing_config = Config::load_from_file(id).unwrap_or_else(|err| {
|
||||
eprintln!("failed to load existing config file! - {err}");
|
||||
process::exit(1)
|
||||
});
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::Parser;
|
||||
use log::{debug, info};
|
||||
|
||||
use coconut_bandwidth_contract_common::msg::InstantiateMsg;
|
||||
use validator_client::nyxd::AccountId;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -12,7 +15,7 @@ pub struct Args {
|
||||
pub pool_addr: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub multisig_addr: Option<String>,
|
||||
pub multisig_addr: Option<AccountId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub mix_denom: Option<String>,
|
||||
@@ -24,8 +27,10 @@ pub async fn generate(args: Args) {
|
||||
debug!("Received arguments: {:?}", args);
|
||||
|
||||
let multisig_addr = args.multisig_addr.unwrap_or_else(|| {
|
||||
std::env::var(network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS)
|
||||
.expect("Multisig address has to be set")
|
||||
let address = std::env::var(network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS)
|
||||
.expect("Multisig address has to be set");
|
||||
AccountId::from_str(address.as_str())
|
||||
.expect("Failed converting multisig address to AccountId")
|
||||
});
|
||||
|
||||
let mix_denom = args.mix_denom.unwrap_or_else(|| {
|
||||
@@ -34,7 +39,7 @@ pub async fn generate(args: Args) {
|
||||
|
||||
let instantiate_msg = InstantiateMsg {
|
||||
pool_addr: args.pool_addr,
|
||||
multisig_addr,
|
||||
multisig_addr: multisig_addr.to_string(),
|
||||
mix_denom,
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::str::FromStr;
|
||||
|
||||
use coconut_dkg_common::msg::InstantiateMsg;
|
||||
use coconut_dkg_common::types::TimeConfiguration;
|
||||
use validator_client::nyxd::AccountId;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -14,7 +15,7 @@ pub struct Args {
|
||||
pub group_addr: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub multisig_addr: Option<String>,
|
||||
pub multisig_addr: Option<AccountId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub public_key_submission_time_secs: Option<u64>,
|
||||
@@ -44,8 +45,10 @@ pub async fn generate(args: Args) {
|
||||
debug!("Received arguments: {:?}", args);
|
||||
|
||||
let multisig_addr = args.multisig_addr.unwrap_or_else(|| {
|
||||
std::env::var(network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS)
|
||||
.expect("Multisig address has to be set")
|
||||
let address = std::env::var(network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS)
|
||||
.expect("Multisig address has to be set");
|
||||
AccountId::from_str(address.as_str())
|
||||
.expect("Failed converting multisig address to AccountId")
|
||||
});
|
||||
|
||||
let mix_denom = args.mix_denom.unwrap_or_else(|| {
|
||||
@@ -86,7 +89,7 @@ pub async fn generate(args: Args) {
|
||||
|
||||
let instantiate_msg = InstantiateMsg {
|
||||
group_addr: args.group_addr,
|
||||
multisig_addr,
|
||||
multisig_addr: multisig_addr.to_string(),
|
||||
time_configuration: Some(time_configuration),
|
||||
mix_denom,
|
||||
};
|
||||
|
||||
@@ -6,15 +6,17 @@ use log::{debug, info};
|
||||
|
||||
use cosmwasm_std::Decimal;
|
||||
use mixnet_contract_common::{InitialRewardingParams, InstantiateMsg, Percent};
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use validator_client::nyxd::AccountId;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub rewarding_validator_address: Option<String>,
|
||||
pub rewarding_validator_address: Option<AccountId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub vesting_contract_address: Option<String>,
|
||||
pub vesting_contract_address: Option<AccountId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub rewarding_denom: Option<String>,
|
||||
@@ -77,13 +79,17 @@ pub async fn generate(args: Args) {
|
||||
debug!("initial_rewarding_params: {:?}", initial_rewarding_params);
|
||||
|
||||
let rewarding_validator_address = args.rewarding_validator_address.unwrap_or_else(|| {
|
||||
std::env::var(network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS)
|
||||
.expect("Rewarding validator address has to be set")
|
||||
let address = std::env::var(network_defaults::var_names::REWARDING_VALIDATOR_ADDRESS)
|
||||
.expect("Rewarding validator address has to be set");
|
||||
AccountId::from_str(address.as_str())
|
||||
.expect("Failed converting rewarding validator address to AccountId")
|
||||
});
|
||||
|
||||
let vesting_contract_address = args.vesting_contract_address.unwrap_or_else(|| {
|
||||
std::env::var(network_defaults::var_names::VESTING_CONTRACT_ADDRESS)
|
||||
.expect("Vesting contract address has to be set")
|
||||
let address = std::env::var(network_defaults::var_names::VESTING_CONTRACT_ADDRESS)
|
||||
.expect("Vesting contract address has to be set");
|
||||
AccountId::from_str(address.as_str())
|
||||
.expect("Failed converting vesting contract address to AccountId")
|
||||
});
|
||||
|
||||
let rewarding_denom = args.rewarding_denom.unwrap_or_else(|| {
|
||||
@@ -92,8 +98,8 @@ pub async fn generate(args: Args) {
|
||||
});
|
||||
|
||||
let instantiate_msg = InstantiateMsg {
|
||||
rewarding_validator_address,
|
||||
vesting_contract_address,
|
||||
rewarding_validator_address: rewarding_validator_address.to_string(),
|
||||
vesting_contract_address: vesting_contract_address.to_string(),
|
||||
rewarding_denom,
|
||||
epochs_in_interval: args.epochs_in_interval,
|
||||
epoch_duration: Duration::from_secs(args.epoch_duration),
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use log::{debug, info};
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::Parser;
|
||||
use cosmwasm_std::Decimal;
|
||||
use cw_utils::{Duration, Threshold};
|
||||
use log::{debug, info};
|
||||
use multisig_contract_common::msg::InstantiateMsg;
|
||||
use validator_client::nyxd::AccountId;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -20,10 +22,10 @@ pub struct Args {
|
||||
pub max_voting_period: u64,
|
||||
|
||||
#[clap(long)]
|
||||
pub coconut_bandwidth_contract_address: Option<String>,
|
||||
pub coconut_bandwidth_contract_address: Option<AccountId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub coconut_dkg_contract_address: Option<String>,
|
||||
pub coconut_dkg_contract_address: Option<AccountId>,
|
||||
}
|
||||
|
||||
pub async fn generate(args: Args) {
|
||||
@@ -33,13 +35,18 @@ pub async fn generate(args: Args) {
|
||||
|
||||
let coconut_bandwidth_contract_address =
|
||||
args.coconut_bandwidth_contract_address.unwrap_or_else(|| {
|
||||
std::env::var(network_defaults::var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS)
|
||||
.expect("Coconut bandwidth contract address has to be set")
|
||||
let address =
|
||||
std::env::var(network_defaults::var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS)
|
||||
.expect("Coconut bandwidth contract address has to be set");
|
||||
AccountId::from_str(address.as_str())
|
||||
.expect("Failed converting bandwidth contract address to AccountId")
|
||||
});
|
||||
|
||||
let coconut_dkg_contract_address = args.coconut_dkg_contract_address.unwrap_or_else(|| {
|
||||
std::env::var(network_defaults::var_names::COCONUT_DKG_CONTRACT_ADDRESS)
|
||||
.expect("Coconut DKG contract address has to be set")
|
||||
let address = std::env::var(network_defaults::var_names::COCONUT_DKG_CONTRACT_ADDRESS)
|
||||
.expect("Coconut DKG contract address has to be set");
|
||||
AccountId::from_str(address.as_str())
|
||||
.expect("Failed converting DKG contract address to AccountId")
|
||||
});
|
||||
|
||||
let instantiate_msg = InstantiateMsg {
|
||||
@@ -49,8 +56,8 @@ pub async fn generate(args: Args) {
|
||||
.expect("threshold can't be converted to Decimal"),
|
||||
},
|
||||
max_voting_period: Duration::Time(args.max_voting_period),
|
||||
coconut_bandwidth_contract_address,
|
||||
coconut_dkg_contract_address,
|
||||
coconut_bandwidth_contract_address: coconut_bandwidth_contract_address.to_string(),
|
||||
coconut_dkg_contract_address: coconut_dkg_contract_address.to_string(),
|
||||
};
|
||||
|
||||
debug!("instantiate_msg: {:?}", instantiate_msg);
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::Parser;
|
||||
use log::{debug, info};
|
||||
|
||||
use validator_client::nyxd::AccountId;
|
||||
use vesting_contract_common::InitMsg;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub mixnet_contract_address: Option<String>,
|
||||
pub mixnet_contract_address: Option<AccountId>,
|
||||
|
||||
#[clap(long)]
|
||||
pub mix_denom: Option<String>,
|
||||
@@ -21,8 +24,10 @@ pub async fn generate(args: Args) {
|
||||
debug!("Received arguments: {:?}", args);
|
||||
|
||||
let mixnet_contract_address = args.mixnet_contract_address.unwrap_or_else(|| {
|
||||
std::env::var(network_defaults::var_names::MIXNET_CONTRACT_ADDRESS)
|
||||
.expect("Mixnet contract address has to be set")
|
||||
let address = std::env::var(network_defaults::var_names::MIXNET_CONTRACT_ADDRESS)
|
||||
.expect("Mixnet contract address has to be set");
|
||||
AccountId::from_str(address.as_str())
|
||||
.expect("Failed converting mixnet address to AccountId")
|
||||
});
|
||||
|
||||
let mix_denom = args.mix_denom.unwrap_or_else(|| {
|
||||
@@ -30,7 +35,7 @@ pub async fn generate(args: Args) {
|
||||
});
|
||||
|
||||
let instantiate_msg = InitMsg {
|
||||
mixnet_contract_address,
|
||||
mixnet_contract_address: mixnet_contract_address.to_string(),
|
||||
mix_denom,
|
||||
};
|
||||
|
||||
|
||||
+11
-27
@@ -30,23 +30,15 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
|
||||
fn default_root_directory() -> PathBuf;
|
||||
|
||||
// default, most probable, implementations; can be easily overridden where required
|
||||
fn default_config_directory(id: Option<&str>) -> PathBuf {
|
||||
if let Some(id) = id {
|
||||
Self::default_root_directory().join(id).join(CONFIG_DIR)
|
||||
} else {
|
||||
Self::default_root_directory().join(CONFIG_DIR)
|
||||
}
|
||||
fn default_config_directory(id: &str) -> PathBuf {
|
||||
Self::default_root_directory().join(id).join(CONFIG_DIR)
|
||||
}
|
||||
|
||||
fn default_data_directory(id: Option<&str>) -> PathBuf {
|
||||
if let Some(id) = id {
|
||||
Self::default_root_directory().join(id).join(DATA_DIR)
|
||||
} else {
|
||||
Self::default_root_directory().join(DATA_DIR)
|
||||
}
|
||||
fn default_data_directory(id: &str) -> PathBuf {
|
||||
Self::default_root_directory().join(id).join(DATA_DIR)
|
||||
}
|
||||
|
||||
fn default_config_file_path(id: Option<&str>) -> PathBuf {
|
||||
fn default_config_file_path(id: &str) -> PathBuf {
|
||||
Self::default_config_directory(id).join(Self::config_file_name())
|
||||
}
|
||||
|
||||
@@ -54,23 +46,15 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
|
||||
|
||||
fn try_default_root_directory() -> Option<PathBuf>;
|
||||
|
||||
fn try_default_config_directory(id: Option<&str>) -> Option<PathBuf> {
|
||||
if let Some(id) = id {
|
||||
Self::try_default_root_directory().map(|d| d.join(id).join(CONFIG_DIR))
|
||||
} else {
|
||||
Self::try_default_root_directory().map(|d| d.join(CONFIG_DIR))
|
||||
}
|
||||
fn try_default_config_directory(id: &str) -> Option<PathBuf> {
|
||||
Self::try_default_root_directory().map(|d| d.join(id).join(CONFIG_DIR))
|
||||
}
|
||||
|
||||
fn try_default_data_directory(id: Option<&str>) -> Option<PathBuf> {
|
||||
if let Some(id) = id {
|
||||
Self::try_default_root_directory().map(|d| d.join(id).join(DATA_DIR))
|
||||
} else {
|
||||
Self::try_default_root_directory().map(|d| d.join(DATA_DIR))
|
||||
}
|
||||
fn try_default_data_directory(id: &str) -> Option<PathBuf> {
|
||||
Self::try_default_root_directory().map(|d| d.join(id).join(DATA_DIR))
|
||||
}
|
||||
|
||||
fn try_default_config_file_path(id: Option<&str>) -> Option<PathBuf> {
|
||||
fn try_default_config_file_path(id: &str) -> Option<PathBuf> {
|
||||
Self::try_default_config_directory(id).map(|d| d.join(Self::config_file_name()))
|
||||
}
|
||||
|
||||
@@ -113,7 +97,7 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_from_file(id: Option<&str>) -> io::Result<Self> {
|
||||
fn load_from_file(id: &str) -> io::Result<Self> {
|
||||
let file = Self::default_config_file_path(id);
|
||||
log::trace!("Loading from file: {:#?}", file);
|
||||
let config_contents = fs::read_to_string(file)?;
|
||||
|
||||
@@ -13,3 +13,4 @@ cw4 = { version = "0.13.4" }
|
||||
cosmwasm-std = "1.0.0"
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = { version = "1.0.23" }
|
||||
|
||||
+3
@@ -1,3 +1,6 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::StdError;
|
||||
use cw_utils::ThresholdError;
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod error;
|
||||
pub mod msg;
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{entry_point, Addr, Coin, DepsMut, Empty, Env, Response};
|
||||
use cw3_flex_multisig::{state::CONFIG, ContractError};
|
||||
use cw3_flex_multisig::state::CONFIG;
|
||||
use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper};
|
||||
use multisig_contract_common::error::ContractError;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ cw-storage-plus = { version = "0.13.4" }
|
||||
cosmwasm-std = { version = "1.0.0" }
|
||||
schemars = "0.8.1"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = { version = "1.0.23" }
|
||||
|
||||
group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" }
|
||||
multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts/multisig-contract" }
|
||||
|
||||
@@ -17,8 +17,8 @@ use cw4::{Cw4Contract, MemberChangedHookMsg, MemberDiff};
|
||||
use cw_storage_plus::Bound;
|
||||
use cw_utils::{maybe_addr, Expiration, ThresholdResponse};
|
||||
|
||||
use crate::error::ContractError;
|
||||
use crate::state::{Config, CONFIG};
|
||||
use multisig_contract_common::error::ContractError;
|
||||
use multisig_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
||||
|
||||
// version info for migration info
|
||||
|
||||
@@ -1,5 +1,2 @@
|
||||
pub mod contract;
|
||||
pub mod error;
|
||||
pub mod state;
|
||||
|
||||
pub use crate::error::ContractError;
|
||||
|
||||
+4
-4
@@ -12,10 +12,10 @@ DENOMS_EXPONENT=6
|
||||
MIXNET_CONTRACT_ADDRESS=n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g
|
||||
VESTING_CONTRACT_ADDRESS=n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw
|
||||
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n12ckdkm3q7eytefs7rwu4ue3t9hxgvl9v08jddmtwgct2ve0pv50q0t8dlt
|
||||
GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m
|
||||
MULTISIG_CONTRACT_ADDRESS=n14krxe8ukzagwhvec0rmteexu62w8k9kp9sra9ww6em2hnmzcukqsa0utc8
|
||||
COCONUT_DKG_CONTRACT_ADDRESS=n1rl5n6cxuz2hdy3f7d9hsnw8zn0zwwwr0r4dxfz7tktgpgkcnz9zshmvksc
|
||||
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19d2nwj7fdhxqmyvgy8lf3ad49a6vmww4shryhrkj2mqk36att66s6xzszw
|
||||
GROUP_CONTRACT_ADDRESS=n1fqquzw4mk0pkamgr2ywt2v7h2j9nuyjjn4gvpy8zlpp6xn0uyuzqfm28l5
|
||||
MULTISIG_CONTRACT_ADDRESS=n1gaq3666chd5348apj8cka8t2mckv7azp9espyr7wgpxyuzur5d0sazpysy
|
||||
COCONUT_DKG_CONTRACT_ADDRESS=n18yadscxw8v35dds7ksv3j0svmjh3h6e7tmxpadk96mvgz27zygkshuf4vs
|
||||
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090"
|
||||
NYXD="https://qwerty-validator.qa.nymte.ch/"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.1.7"
|
||||
version = "1.1.9"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
## UNRELEASED
|
||||
|
||||
- nothing yet
|
||||
|
||||
## [nym-explorer-v1.0.5](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.5) (2023-02-14)
|
||||
|
||||
- NE - link `Owner` field on the node detail page to the account details on NG explorer ([#2923])
|
||||
- NE - Upgrade Sandbox and make below changes: ([#2332])
|
||||
|
||||
[#2923]: https://github.com/nymtech/nym/issues/2923
|
||||
[#2332]: https://github.com/nymtech/nym/issues/2332
|
||||
|
||||
## [nym-explorer-v1.0.4](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.4) (2023-01-31)
|
||||
|
||||
- Add routing score on gateway list ([#2913])
|
||||
- Add gateway's last Routing Score to the gateways list page ([#2186])
|
||||
- Upgrade Sandbox and make below changes: ([#2332])
|
||||
|
||||
[#2913]: https://github.com/nymtech/nym/pull/2913
|
||||
[#2186]: https://github.com/nymtech/nym/issues/2186
|
||||
[#2332]: https://github.com/nymtech/nym/issues/2332
|
||||
|
||||
## [nym-explorer-v1.0.3](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.3) (2023-01-24)
|
||||
|
||||
- Stake Saturation tooltip on node list and node pages updated ([#2877])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nym/network-explorer",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.5",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -120,4 +120,4 @@
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ export const OVERVIEW_API = `${API_BASE_URL}/overview`;
|
||||
export const MIXNODE_PING = `${API_BASE_URL}/ping`;
|
||||
export const MIXNODES_API = `${API_BASE_URL}/mix-nodes`;
|
||||
export const MIXNODE_API = `${API_BASE_URL}/mix-node`;
|
||||
export const GATEWAYS_API = `${NYM_API_BASE_URL}/api/v1/gateways`;
|
||||
export const GATEWAYS_API = `${NYM_API_BASE_URL}/api/v1/status/gateways/detailed`;
|
||||
export const VALIDATORS_API = `${VALIDATOR_BASE_URL}/validators`;
|
||||
export const BLOCK_API = `${NYM_API_BASE_URL}/block`;
|
||||
export const COUNTRY_DATA_API = `${API_BASE_URL}/countries`;
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
CountryDataResponse,
|
||||
DelegationsResponse,
|
||||
UniqDelegationsResponse,
|
||||
GatewayResponse,
|
||||
GatewayReportResponse,
|
||||
UptimeStoryResponse,
|
||||
MixNodeDescriptionResponse,
|
||||
@@ -27,7 +26,10 @@ import {
|
||||
StatusResponse,
|
||||
SummaryOverviewResponse,
|
||||
ValidatorsResponse,
|
||||
GatewayBondAnnotated,
|
||||
GatewayBond,
|
||||
} from '../typeDefs/explorer-api';
|
||||
import { toPercentIntegerString } from '../utils';
|
||||
|
||||
function getFromCache(key: string) {
|
||||
const ts = Number(localStorage.getItem('ts'));
|
||||
@@ -89,9 +91,13 @@ export class Api {
|
||||
return response.json();
|
||||
};
|
||||
|
||||
static fetchGateways = async (): Promise<GatewayResponse> => {
|
||||
static fetchGateways = async (): Promise<GatewayBond[]> => {
|
||||
const res = await fetch(GATEWAYS_API);
|
||||
return res.json();
|
||||
const gatewaysAnnotated: GatewayBondAnnotated[] = await res.json();
|
||||
return gatewaysAnnotated.map(({ gateway_bond, performance }) => ({
|
||||
...gateway_bond,
|
||||
performance: toPercentIntegerString(performance),
|
||||
}));
|
||||
};
|
||||
|
||||
static fetchGatewayUptimeStoryById = async (id: string): Promise<UptimeStoryResponse> =>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
|
||||
import { Link, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Tooltip } from '@nymproject/react/tooltip/Tooltip';
|
||||
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard';
|
||||
@@ -37,9 +37,19 @@ function formatCellValues(val: string | number, field: string) {
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (field === 'bond') {
|
||||
return unymToNym(val, 6);
|
||||
}
|
||||
|
||||
if (field === 'owner') {
|
||||
return (
|
||||
<Link underline="none" color="inherit" target="_blank" href={`https://mixnet.explorers.guru/account/${val}`}>
|
||||
{val}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GatewayResponse, GatewayResponseItem, GatewayReportResponse } from '../typeDefs/explorer-api';
|
||||
import { GatewayResponse, GatewayBond, GatewayReportResponse } from '../typeDefs/explorer-api';
|
||||
|
||||
export type GatewayRowType = {
|
||||
id: string;
|
||||
@@ -8,6 +8,7 @@ export type GatewayRowType = {
|
||||
host: string;
|
||||
location: string;
|
||||
version: string;
|
||||
performance: string;
|
||||
};
|
||||
|
||||
export type GatewayEnrichedRowType = GatewayRowType & {
|
||||
@@ -28,13 +29,11 @@ export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowTy
|
||||
bond: gw.pledge_amount.amount || 0,
|
||||
host: gw.gateway.host || '',
|
||||
version: gw.gateway.version || '',
|
||||
performance: gw.performance,
|
||||
}));
|
||||
}
|
||||
|
||||
export function gatewayEnrichedToGridRow(
|
||||
gateway: GatewayResponseItem,
|
||||
report: GatewayReportResponse,
|
||||
): GatewayEnrichedRowType {
|
||||
export function gatewayEnrichedToGridRow(gateway: GatewayBond, report: GatewayReportResponse): GatewayEnrichedRowType {
|
||||
return {
|
||||
id: gateway.owner,
|
||||
owner: gateway.owner,
|
||||
@@ -47,5 +46,6 @@ export function gatewayEnrichedToGridRow(
|
||||
mixPort: gateway.gateway.mix_port || 0,
|
||||
routingScore: `${report.most_recent}%`,
|
||||
avgUptime: `${report.last_day || report.last_hour}%`,
|
||||
performance: gateway.performance,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { Alert, AlertTitle, Box, CircularProgress, Grid } from '@mui/material';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { GatewayResponseItem } from '../../typeDefs/explorer-api';
|
||||
import { GatewayBond } from '../../typeDefs/explorer-api';
|
||||
import { ColumnsType, DetailTable } from '../../components/DetailTable';
|
||||
import { gatewayEnrichedToGridRow, GatewayEnrichedRowType } from '../../components/Gateways';
|
||||
import { ComponentError } from '../../components/ComponentError';
|
||||
@@ -69,7 +69,7 @@ const columns: ColumnsType[] = [
|
||||
/**
|
||||
* Shows gateway details
|
||||
*/
|
||||
const PageGatewayDetailsWithState = ({ selectedGateway }: { selectedGateway: GatewayResponseItem | undefined }) => {
|
||||
const PageGatewayDetailsWithState = ({ selectedGateway }: { selectedGateway: GatewayBond | undefined }) => {
|
||||
const [enrichGateway, setEnrichGateway] = React.useState<GatewayEnrichedRowType>();
|
||||
const [status, setStatus] = React.useState<number[] | undefined>();
|
||||
const { uptimeReport, uptimeStory } = useGatewayContext();
|
||||
@@ -130,7 +130,7 @@ const PageGatewayDetailsWithState = ({ selectedGateway }: { selectedGateway: Gat
|
||||
* Guard component to handle loading and not found states
|
||||
*/
|
||||
const PageGatewayDetailGuard: FCWithChildren = () => {
|
||||
const [selectedGateway, setSelectedGateway] = React.useState<GatewayResponseItem | undefined>();
|
||||
const [selectedGateway, setSelectedGateway] = React.useState<GatewayBond | undefined>();
|
||||
const { gateways } = useMainContext();
|
||||
const { id } = useParams<{ id: string | undefined }>();
|
||||
|
||||
|
||||
@@ -81,6 +81,24 @@ export const PageGateways: FCWithChildren = () => {
|
||||
</MuiLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'performance',
|
||||
headerName: 'Routing Score',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Routing Score" />,
|
||||
width: 150,
|
||||
headerAlign: 'left',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<MuiLink
|
||||
sx={{ ...cellStyles }}
|
||||
component={RRDLink}
|
||||
to={`/network-components/gateway/${params.row.identityKey}`}
|
||||
data-testid="pledge-amount"
|
||||
>
|
||||
{`${params.value}%`}
|
||||
</MuiLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'host',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="IP:Port" />,
|
||||
|
||||
@@ -116,15 +116,21 @@ export interface StatsResponse {
|
||||
|
||||
export type MixNodeHistoryResponse = StatsResponse;
|
||||
|
||||
export interface GatewayResponseItem {
|
||||
export interface GatewayBond {
|
||||
block_height: number;
|
||||
pledge_amount: Amount;
|
||||
total_delegation: Amount;
|
||||
owner: string;
|
||||
gateway: Gateway;
|
||||
performance: string;
|
||||
}
|
||||
|
||||
export type GatewayResponse = GatewayResponseItem[];
|
||||
export interface GatewayBondAnnotated {
|
||||
gateway_bond: GatewayBond;
|
||||
performance: string;
|
||||
}
|
||||
|
||||
export type GatewayResponse = GatewayBond[];
|
||||
|
||||
export interface GatewayReportResponse {
|
||||
identity: string;
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-gateway"
|
||||
version = "1.1.7"
|
||||
version = "1.1.9"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
@@ -55,7 +55,8 @@ pub struct Init {
|
||||
long,
|
||||
alias = "validators",
|
||||
alias = "nymd_validators",
|
||||
value_delimiter = ','
|
||||
value_delimiter = ',',
|
||||
hide = true
|
||||
)]
|
||||
// the alias here is included for backwards compatibility (1.1.4 and before)
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
@@ -66,7 +67,7 @@ pub struct Init {
|
||||
|
||||
/// Set this gateway to work only with coconut credentials; that would disallow clients to
|
||||
/// bypass bandwidth credential requirement
|
||||
#[clap(long)]
|
||||
#[clap(long, hide = true)]
|
||||
only_coconut_credentials: Option<bool>,
|
||||
|
||||
/// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server
|
||||
@@ -102,7 +103,7 @@ impl From<Init> for OverrideConfig {
|
||||
pub async fn execute(args: Init, output: OutputFormat) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
println!("Initialising gateway {}...", args.id);
|
||||
|
||||
let already_init = if Config::default_config_file_path(Some(&args.id)).exists() {
|
||||
let already_init = if Config::default_config_file_path(&args.id).exists() {
|
||||
eprintln!(
|
||||
"Gateway \"{}\" was already initialised before! Config information will be \
|
||||
overwritten (but keys will be kept)!",
|
||||
|
||||
@@ -53,7 +53,8 @@ pub struct Run {
|
||||
long,
|
||||
alias = "validators",
|
||||
alias = "nymd_validators",
|
||||
value_delimiter = ','
|
||||
value_delimiter = ',',
|
||||
hide = true
|
||||
)]
|
||||
// the alias here is included for backwards compatibility (1.1.4 and before)
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
@@ -64,7 +65,7 @@ pub struct Run {
|
||||
|
||||
/// Set this gateway to work only with coconut credentials; that would disallow clients to
|
||||
/// bypass bandwidth credential requirement
|
||||
#[clap(long)]
|
||||
#[clap(long, hide = true)]
|
||||
only_coconut_credentials: Option<bool>,
|
||||
|
||||
/// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server
|
||||
|
||||
@@ -139,7 +139,7 @@ fn do_upgrade(mut config: Config, args: &Upgrade, package_version: Version) {
|
||||
pub async fn execute(args: &Upgrade) {
|
||||
let package_version = parse_package_version();
|
||||
|
||||
let existing_config = Config::load_from_file(Some(&args.id)).unwrap_or_else(|err| {
|
||||
let existing_config = Config::load_from_file(&args.id).unwrap_or_else(|err| {
|
||||
eprintln!("failed to load existing config file! - {err}");
|
||||
process::exit(1)
|
||||
});
|
||||
|
||||
@@ -374,23 +374,23 @@ pub struct Gateway {
|
||||
|
||||
impl Gateway {
|
||||
fn default_private_sphinx_key_file(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(Some(id)).join("private_sphinx.pem")
|
||||
Config::default_data_directory(id).join("private_sphinx.pem")
|
||||
}
|
||||
|
||||
fn default_public_sphinx_key_file(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(Some(id)).join("public_sphinx.pem")
|
||||
Config::default_data_directory(id).join("public_sphinx.pem")
|
||||
}
|
||||
|
||||
fn default_private_identity_key_file(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(Some(id)).join("private_identity.pem")
|
||||
Config::default_data_directory(id).join("private_identity.pem")
|
||||
}
|
||||
|
||||
fn default_public_identity_key_file(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(Some(id)).join("public_identity.pem")
|
||||
Config::default_data_directory(id).join("public_identity.pem")
|
||||
}
|
||||
|
||||
fn default_database_path(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(Some(id)).join("db.sqlite")
|
||||
Config::default_data_directory(id).join("db.sqlite")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -181,6 +181,7 @@ impl<St: Storage> ConnectionHandler<St> {
|
||||
mut shutdown: TaskClient,
|
||||
) {
|
||||
debug!("Starting connection handler for {:?}", remote);
|
||||
shutdown.mark_as_success();
|
||||
let mut framed_conn = Framed::new(conn, SphinxCodec);
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
|
||||
@@ -11,14 +11,14 @@ pub(crate) fn build_config<O: Into<OverrideConfig>>(
|
||||
id: String,
|
||||
override_args: O,
|
||||
) -> Result<Config, GatewayError> {
|
||||
let config = match Config::load_from_file(Some(&id)) {
|
||||
let config = match Config::load_from_file(&id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})",
|
||||
);
|
||||
return Err(GatewayError::ConfigLoadFailure {
|
||||
path: Config::default_config_file_path(Some(&id)),
|
||||
path: Config::default_config_file_path(&id),
|
||||
id,
|
||||
source: err,
|
||||
});
|
||||
|
||||
+3
-2
@@ -4,7 +4,8 @@
|
||||
"nym-wallet",
|
||||
"nym-connect",
|
||||
"nym-connect-android",
|
||||
"sdk/typescript/**"
|
||||
"sdk/typescript/examples/docs",
|
||||
"sdk/typescript/packages/**"
|
||||
],
|
||||
"version": "0.0.0"
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-mixnode"
|
||||
version = "1.1.8"
|
||||
version = "1.1.10"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
@@ -41,7 +41,7 @@ fn read_user_input() -> String {
|
||||
|
||||
pub(crate) fn execute(args: Describe) {
|
||||
// ensure that the mixnode has in fact been initialized
|
||||
match Config::load_from_file(Some(&args.id)) {
|
||||
match Config::load_from_file(&args.id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", &args.id);
|
||||
@@ -83,7 +83,7 @@ pub(crate) fn execute(args: Describe) {
|
||||
// save the struct
|
||||
NodeDescription::save_to_file(
|
||||
&node_description,
|
||||
Config::default_config_directory(Some(&args.id)),
|
||||
Config::default_config_directory(&args.id),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ pub(crate) fn execute(args: &Init, output: OutputFormat) {
|
||||
let id = &override_config_fields.id;
|
||||
eprintln!("Initialising mixnode {id}...");
|
||||
|
||||
let already_init = if Config::default_config_file_path(Some(id)).exists() {
|
||||
let already_init = if Config::default_config_file_path(id).exists() {
|
||||
eprintln!("Mixnode \"{id}\" was already initialised before! Config information will be overwritten (but keys will be kept)!");
|
||||
true
|
||||
} else {
|
||||
|
||||
@@ -15,7 +15,7 @@ pub(crate) struct NodeDetails {
|
||||
}
|
||||
|
||||
pub(crate) fn execute(args: &NodeDetails, output: OutputFormat) {
|
||||
let config = match Config::load_from_file(Some(&args.id)) {
|
||||
let config = match Config::load_from_file(&args.id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!(
|
||||
|
||||
@@ -79,7 +79,7 @@ fn special_addresses() -> Vec<&'static str> {
|
||||
pub(crate) async fn execute(args: &Run, output: OutputFormat) {
|
||||
eprintln!("Starting mixnode {}...", args.id);
|
||||
|
||||
let mut config = match Config::load_from_file(Some(&args.id)) {
|
||||
let mut config = match Config::load_from_file(&args.id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!(
|
||||
|
||||
@@ -71,7 +71,7 @@ fn print_signed_text(private_key: &identity::PrivateKey, text: &str) {
|
||||
}
|
||||
|
||||
pub(crate) fn execute(args: &Sign) {
|
||||
let config = match Config::load_from_file(Some(&args.id)) {
|
||||
let config = match Config::load_from_file(&args.id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!(
|
||||
|
||||
@@ -125,7 +125,7 @@ fn do_upgrade(mut config: Config, args: &Upgrade, package_version: Version) {
|
||||
pub(crate) fn execute(args: &Upgrade) {
|
||||
let package_version = parse_package_version();
|
||||
|
||||
let existing_config = Config::load_from_file(Some(&args.id)).unwrap_or_else(|err| {
|
||||
let existing_config = Config::load_from_file(&args.id).unwrap_or_else(|err| {
|
||||
eprintln!("failed to load existing config file! - {err}");
|
||||
process::exit(1)
|
||||
});
|
||||
|
||||
@@ -376,19 +376,19 @@ struct MixNode {
|
||||
|
||||
impl MixNode {
|
||||
fn default_private_identity_key_file(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(Some(id)).join("private_identity.pem")
|
||||
Config::default_data_directory(id).join("private_identity.pem")
|
||||
}
|
||||
|
||||
fn default_public_identity_key_file(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(Some(id)).join("public_identity.pem")
|
||||
Config::default_data_directory(id).join("public_identity.pem")
|
||||
}
|
||||
|
||||
fn default_private_sphinx_key_file(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(Some(id)).join("private_sphinx.pem")
|
||||
Config::default_data_directory(id).join("private_sphinx.pem")
|
||||
}
|
||||
|
||||
fn default_public_sphinx_key_file(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(Some(id)).join("public_sphinx.pem")
|
||||
Config::default_data_directory(id).join("public_sphinx.pem")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ impl ConnectionHandler {
|
||||
mut shutdown: TaskClient,
|
||||
) {
|
||||
debug!("Starting connection handler for {:?}", remote);
|
||||
shutdown.mark_as_success();
|
||||
let mut framed_conn = Framed::new(conn, SphinxCodec);
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
|
||||
@@ -58,7 +58,7 @@ impl MixNode {
|
||||
}
|
||||
|
||||
fn load_node_description(config: &Config) -> NodeDescription {
|
||||
NodeDescription::load_from_file(Config::default_config_directory(Some(&config.get_id())))
|
||||
NodeDescription::load_from_file(Config::default_config_directory(&config.get_id()))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-api"
|
||||
version = "1.1.7"
|
||||
version = "1.1.10"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
@@ -291,7 +291,7 @@ pub struct GatewayUptimeHistoryResponse {
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct CirculatingSupplyResponse {
|
||||
pub initial_supply: Coin,
|
||||
pub total_supply: Coin,
|
||||
pub mixmining_reserve: Coin,
|
||||
pub vesting_tokens: Coin,
|
||||
pub circulating_supply: Coin,
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ use validator_client::nyxd::Coin;
|
||||
|
||||
pub(crate) struct CirculatingSupplyCacheData {
|
||||
// no need to cache that one as it's constant, but let's put it here for consistency sake
|
||||
pub(crate) initial_supply: Coin,
|
||||
pub(crate) total_supply: Coin,
|
||||
pub(crate) mixmining_reserve: Cache<Coin>,
|
||||
pub(crate) vesting_tokens: Cache<Coin>,
|
||||
pub(crate) circulating_supply: Cache<Coin>,
|
||||
@@ -18,7 +18,7 @@ impl CirculatingSupplyCacheData {
|
||||
let zero_coin = Coin::new(0, &mix_denom);
|
||||
|
||||
CirculatingSupplyCacheData {
|
||||
initial_supply: Coin::new(1_000_000_000_000_000, mix_denom),
|
||||
total_supply: Coin::new(1_000_000_000_000_000, mix_denom),
|
||||
mixmining_reserve: Cache::new(zero_coin.clone()),
|
||||
vesting_tokens: Cache::new(zero_coin.clone()),
|
||||
circulating_supply: Cache::new(zero_coin),
|
||||
@@ -29,7 +29,7 @@ impl CirculatingSupplyCacheData {
|
||||
impl<'a> From<&'a CirculatingSupplyCacheData> for CirculatingSupplyResponse {
|
||||
fn from(value: &'a CirculatingSupplyCacheData) -> Self {
|
||||
CirculatingSupplyResponse {
|
||||
initial_supply: value.initial_supply.clone().into(),
|
||||
total_supply: value.total_supply.clone().into(),
|
||||
mixmining_reserve: value.mixmining_reserve.clone().into_inner().into(),
|
||||
vesting_tokens: value.vesting_tokens.clone().into_inner().into(),
|
||||
circulating_supply: value.circulating_supply.clone().into_inner().into(),
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ impl CirculatingSupplyCache {
|
||||
pub(crate) async fn update(&self, mixmining_reserve: Coin, vesting_tokens: Coin) {
|
||||
let mut cache = self.data.write().await;
|
||||
|
||||
let mut circulating_supply = cache.initial_supply.clone();
|
||||
let mut circulating_supply = cache.total_supply.clone();
|
||||
circulating_supply.amount -= mixmining_reserve.amount;
|
||||
circulating_supply.amount -= vesting_tokens.amount;
|
||||
|
||||
|
||||
@@ -15,7 +15,11 @@ pub(crate) mod routes;
|
||||
|
||||
/// Merges the routes with http information and returns it to Rocket for serving
|
||||
pub(crate) fn circulating_supply_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![settings: routes::get_circulating_supply]
|
||||
openapi_get_routes_spec![
|
||||
settings: routes::get_full_circulating_supply,
|
||||
routes::get_total_supply,
|
||||
routes::get_circulating_supply
|
||||
]
|
||||
}
|
||||
|
||||
/// Spawn the circulating supply cache refresher.
|
||||
|
||||
@@ -1,15 +1,30 @@
|
||||
use rocket::http::Status;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::circulating_supply_api::cache::CirculatingSupplyCache;
|
||||
use crate::node_status_api::models::ErrorResponse;
|
||||
use nym_api_requests::models::CirculatingSupplyResponse;
|
||||
use rocket::http::Status;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
use rocket_okapi::openapi;
|
||||
use validator_client::nyxd::Coin;
|
||||
|
||||
// TODO: this is not the best place to put it, it should be more centralised,
|
||||
// but for a quick fix, that's good enough for now...
|
||||
// (for proper solution we should be managing `NymNetworkDetails` via rocket and grabbing display exponent
|
||||
// value from the mix denom here.
|
||||
const UNYM_RATIO: f64 = 1000000.;
|
||||
|
||||
fn unym_coin_to_float_unym(coin: Coin) -> f64 {
|
||||
// our total supply can't exceed 1B so an overflow here is impossible
|
||||
// (if it happened, then we SHOULD crash)
|
||||
coin.amount as f64 / UNYM_RATIO
|
||||
}
|
||||
|
||||
#[openapi(tag = "circulating-supply")]
|
||||
#[get("/circulating-supply")]
|
||||
pub(crate) async fn get_circulating_supply(
|
||||
pub(crate) async fn get_full_circulating_supply(
|
||||
cache: &State<CirculatingSupplyCache>,
|
||||
) -> Result<Json<CirculatingSupplyResponse>, ErrorResponse> {
|
||||
match cache.get_circulating_supply().await {
|
||||
@@ -20,3 +35,43 @@ pub(crate) async fn get_circulating_supply(
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[openapi(tag = "circulating-supply")]
|
||||
#[get("/circulating-supply/total-supply-value")]
|
||||
pub(crate) async fn get_total_supply(
|
||||
cache: &State<CirculatingSupplyCache>,
|
||||
) -> Result<Json<f64>, ErrorResponse> {
|
||||
let full_circulating_supply = match cache.get_circulating_supply().await {
|
||||
Some(res) => res,
|
||||
None => {
|
||||
return Err(ErrorResponse::new(
|
||||
"unavailable",
|
||||
Status::InternalServerError,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Json(unym_coin_to_float_unym(
|
||||
full_circulating_supply.total_supply.into(),
|
||||
)))
|
||||
}
|
||||
|
||||
#[openapi(tag = "circulating-supply")]
|
||||
#[get("/circulating-supply/circulating-supply-value")]
|
||||
pub(crate) async fn get_circulating_supply(
|
||||
cache: &State<CirculatingSupplyCache>,
|
||||
) -> Result<Json<f64>, ErrorResponse> {
|
||||
let full_circulating_supply = match cache.get_circulating_supply().await {
|
||||
Some(res) => res,
|
||||
None => {
|
||||
return Err(ErrorResponse::new(
|
||||
"unavailable",
|
||||
Status::InternalServerError,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Json(unym_coin_to_float_unym(
|
||||
full_circulating_supply.circulating_supply.into(),
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::coconut::dkg::client::DkgClient;
|
||||
use crate::coconut::dkg::complaints::ComplaintReason;
|
||||
use crate::coconut::dkg::state::{ConsistentState, State};
|
||||
use crate::coconut::error::CoconutError;
|
||||
use crate::coconut::helpers::accepted_vote_err;
|
||||
use coconut_dkg_common::event_attributes::DKG_PROPOSAL_ID;
|
||||
use coconut_dkg_common::types::{NodeIndex, TOTAL_DEALINGS};
|
||||
use coconut_dkg_common::verification_key::owner_from_cosmos_msgs;
|
||||
@@ -203,21 +204,23 @@ pub(crate) async fn verification_key_validation(
|
||||
.iter()
|
||||
.position(|node_index| contract_share.node_index == *node_index)
|
||||
{
|
||||
if !check_vk_pairing(¶ms, &recovered_partials[idx], &vk) {
|
||||
let ret = if !check_vk_pairing(¶ms, &recovered_partials[idx], &vk) {
|
||||
dkg_client
|
||||
.vote_verification_key_share(proposal_id, false)
|
||||
.await?;
|
||||
.await
|
||||
} else {
|
||||
dkg_client
|
||||
.vote_verification_key_share(proposal_id, true)
|
||||
.await?;
|
||||
}
|
||||
.await
|
||||
};
|
||||
accepted_vote_err(ret)?;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
dkg_client
|
||||
let ret = dkg_client
|
||||
.vote_verification_key_share(proposal_id, false)
|
||||
.await?
|
||||
.await;
|
||||
accepted_vote_err(ret)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::coconut::error::CoconutError;
|
||||
use validator_client::nyxd::error::NyxdError::AbciError;
|
||||
|
||||
// If the result is already established, the vote might be redundant and
|
||||
// thus the transaction might fail
|
||||
pub(crate) fn accepted_vote_err(ret: Result<(), CoconutError>) -> Result<(), CoconutError> {
|
||||
if let Err(CoconutError::NyxdError(AbciError { ref log, .. })) = ret {
|
||||
let accepted_err = multisig_contract_common::error::ContractError::NotOpen {}.to_string();
|
||||
// If redundant voting is not the case, error out on all other error variants
|
||||
if !log.value().contains(&accepted_err) {
|
||||
ret?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -5,6 +5,7 @@ use self::comm::APICommunicationChannel;
|
||||
use crate::coconut::client::Client as LocalClient;
|
||||
use crate::coconut::deposit::extract_encryption_key;
|
||||
use crate::coconut::error::{CoconutError, Result};
|
||||
use crate::coconut::helpers::accepted_vote_err;
|
||||
use crate::support::storage::NymApiStorage;
|
||||
use coconut_bandwidth_contract_common::spend_credential::{
|
||||
funds_from_cosmos_msgs, SpendCredentialStatus,
|
||||
@@ -40,6 +41,7 @@ pub(crate) mod comm;
|
||||
mod deposit;
|
||||
pub(crate) mod dkg;
|
||||
pub(crate) mod error;
|
||||
pub(crate) mod helpers;
|
||||
pub(crate) mod keypair;
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests;
|
||||
@@ -301,7 +303,7 @@ pub async fn verify_bandwidth_credential(
|
||||
);
|
||||
|
||||
// Vote yes or no on the proposal based on the verification result
|
||||
state
|
||||
let ret = state
|
||||
.client
|
||||
.vote_proposal(
|
||||
proposal_id,
|
||||
@@ -312,7 +314,8 @@ pub async fn verify_bandwidth_credential(
|
||||
Some(verify_credential_body.gateway_cosmos_addr().to_owned()),
|
||||
)),
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
accepted_vote_err(ret)?;
|
||||
|
||||
Ok(Json(VerifyCredentialResponse::new(vote_yes)))
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ pub(crate) struct CliArgs {
|
||||
|
||||
/// Id of the nym-api we want to run
|
||||
#[clap(long)]
|
||||
pub(crate) id: Option<String>,
|
||||
pub(crate) id: String,
|
||||
|
||||
/// Specifies whether network monitoring is enabled on this API
|
||||
#[clap(short = 'm', long)]
|
||||
@@ -93,21 +93,27 @@ pub(crate) struct CliArgs {
|
||||
pub(crate) enabled_credentials_mode: Option<bool>,
|
||||
|
||||
/// Announced address where coconut clients will connect.
|
||||
#[clap(long)]
|
||||
#[clap(long, hide = true)]
|
||||
pub(crate) announce_address: Option<url::Url>,
|
||||
|
||||
/// Flag to indicate whether coconut signer authority is enabled on this API
|
||||
#[clap(long, requires = "mnemonic", requires = "announce_address")]
|
||||
#[clap(
|
||||
long,
|
||||
requires = "mnemonic",
|
||||
requires = "announce_address",
|
||||
hide = true
|
||||
)]
|
||||
pub(crate) enable_coconut: Option<bool>,
|
||||
}
|
||||
|
||||
pub(crate) fn build_config(args: CliArgs) -> Result<Config> {
|
||||
let id = args.id.clone();
|
||||
|
||||
// try to load config from the file, if it doesn't exist, use default values
|
||||
let id = args.id.as_deref();
|
||||
let (config_from_file, _already_initialized) = match Config::load_from_file(id) {
|
||||
let (config_from_file, already_initialized) = match Config::load_from_file(&id) {
|
||||
Ok(cfg) => (cfg, true),
|
||||
Err(_) => {
|
||||
let config_path = Config::default_config_file_path(id)
|
||||
let config_path = Config::default_config_file_path(&id)
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap();
|
||||
@@ -121,23 +127,20 @@ pub(crate) fn build_config(args: CliArgs) -> Result<Config> {
|
||||
|
||||
let config = override_config(config_from_file, args);
|
||||
|
||||
if !_already_initialized {
|
||||
if !already_initialized {
|
||||
fs::create_dir_all(Config::default_config_directory(&id))
|
||||
.expect("Could not create config directory");
|
||||
fs::create_dir_all(Config::default_data_directory(&id))
|
||||
.expect("Could not create data directory");
|
||||
crate::coconut::dkg::controller::init_keypair(&config)?;
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub(crate) fn override_config(mut config: Config, args: CliArgs) -> Config {
|
||||
if let Some(id) = args.id {
|
||||
fs::create_dir_all(Config::default_config_directory(Some(&id)))
|
||||
.expect("Could not create config directory");
|
||||
fs::create_dir_all(Config::default_data_directory(Some(&id)))
|
||||
.expect("Could not create data directory");
|
||||
config = config.with_id(&id);
|
||||
}
|
||||
|
||||
pub(crate) fn override_config(config: Config, args: CliArgs) -> Config {
|
||||
config
|
||||
.with_id(&args.id)
|
||||
.with_optional(Config::with_custom_nyxd_validator, args.nyxd_validator)
|
||||
.with_optional_env(
|
||||
Config::with_custom_mixnet_contract,
|
||||
|
||||
@@ -201,8 +201,8 @@ pub struct NetworkMonitor {
|
||||
impl NetworkMonitor {
|
||||
pub const DB_FILE: &'static str = "credentials_database.db";
|
||||
|
||||
fn default_credentials_database_path() -> PathBuf {
|
||||
Config::default_data_directory(None).join(Self::DB_FILE)
|
||||
fn default_credentials_database_path(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(id).join(Self::DB_FILE)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ impl Default for NetworkMonitor {
|
||||
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
gateway_connection_timeout: DEFAULT_GATEWAY_CONNECTION_TIMEOUT,
|
||||
packet_delivery_timeout: DEFAULT_PACKET_DELIVERY_TIMEOUT,
|
||||
credentials_database_path: Self::default_credentials_database_path(),
|
||||
credentials_database_path: Default::default(),
|
||||
test_routes: DEFAULT_TEST_ROUTES,
|
||||
minimum_test_routes: DEFAULT_MINIMUM_TEST_ROUTES,
|
||||
route_test_packets: DEFAULT_ROUTE_TEST_PACKETS,
|
||||
@@ -242,15 +242,15 @@ pub struct NodeStatusAPI {
|
||||
impl NodeStatusAPI {
|
||||
pub const DB_FILE: &'static str = "db.sqlite";
|
||||
|
||||
fn default_database_path() -> PathBuf {
|
||||
Config::default_data_directory(None).join(Self::DB_FILE)
|
||||
fn default_database_path(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(id).join(Self::DB_FILE)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NodeStatusAPI {
|
||||
fn default() -> Self {
|
||||
NodeStatusAPI {
|
||||
database_path: Self::default_database_path(),
|
||||
database_path: Default::default(),
|
||||
caching_interval: DEFAULT_NODE_STATUS_CACHE_INTERVAL,
|
||||
}
|
||||
}
|
||||
@@ -342,24 +342,24 @@ impl CoconutSigner {
|
||||
pub const COCONUT_VERIFICATION_KEY_FILE: &'static str = "coconut_verification_key.pem";
|
||||
pub const COCONUT_SECRET_KEY_FILE: &'static str = "coconut_secret_key.pem";
|
||||
|
||||
fn default_coconut_verification_key_path() -> PathBuf {
|
||||
Config::default_data_directory(None).join(Self::COCONUT_VERIFICATION_KEY_FILE)
|
||||
fn default_coconut_verification_key_path(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(id).join(Self::COCONUT_VERIFICATION_KEY_FILE)
|
||||
}
|
||||
|
||||
fn default_coconut_secret_key_path() -> PathBuf {
|
||||
Config::default_data_directory(None).join(Self::COCONUT_SECRET_KEY_FILE)
|
||||
fn default_coconut_secret_key_path(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(id).join(Self::COCONUT_SECRET_KEY_FILE)
|
||||
}
|
||||
|
||||
fn default_dkg_persistent_state_path() -> PathBuf {
|
||||
Config::default_data_directory(None).join(Self::DKG_PERSISTENT_STATE_FILE)
|
||||
fn default_dkg_persistent_state_path(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(id).join(Self::DKG_PERSISTENT_STATE_FILE)
|
||||
}
|
||||
|
||||
fn default_dkg_decryption_key_path() -> PathBuf {
|
||||
Config::default_data_directory(None).join(Self::DKG_DECRYPTION_KEY_FILE)
|
||||
fn default_dkg_decryption_key_path(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(id).join(Self::DKG_DECRYPTION_KEY_FILE)
|
||||
}
|
||||
|
||||
fn default_dkg_public_key_with_proof_path() -> PathBuf {
|
||||
Config::default_data_directory(None).join(Self::DKG_PUBLIC_KEY_WITH_PROOF_FILE)
|
||||
fn default_dkg_public_key_with_proof_path(id: &str) -> PathBuf {
|
||||
Config::default_data_directory(id).join(Self::DKG_PUBLIC_KEY_WITH_PROOF_FILE)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,11 +367,11 @@ impl Default for CoconutSigner {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: Default::default(),
|
||||
dkg_persistent_state_path: CoconutSigner::default_dkg_persistent_state_path(),
|
||||
verification_key_path: CoconutSigner::default_coconut_verification_key_path(),
|
||||
secret_key_path: CoconutSigner::default_coconut_secret_key_path(),
|
||||
decryption_key_path: CoconutSigner::default_dkg_decryption_key_path(),
|
||||
public_key_with_proof_path: CoconutSigner::default_dkg_public_key_with_proof_path(),
|
||||
dkg_persistent_state_path: Default::default(),
|
||||
verification_key_path: Default::default(),
|
||||
secret_key_path: Default::default(),
|
||||
decryption_key_path: Default::default(),
|
||||
public_key_with_proof_path: Default::default(),
|
||||
dkg_contract_polling_rate: DEFAULT_DKG_CONTRACT_POLLING_RATE,
|
||||
}
|
||||
}
|
||||
@@ -384,20 +384,18 @@ impl Config {
|
||||
|
||||
pub fn with_id(mut self, id: &str) -> Self {
|
||||
self.base.id = id.to_string();
|
||||
self.node_status_api.database_path =
|
||||
Config::default_data_directory(Some(id)).join(NodeStatusAPI::DB_FILE);
|
||||
self.node_status_api.database_path = NodeStatusAPI::default_database_path(id);
|
||||
self.network_monitor.credentials_database_path =
|
||||
Config::default_data_directory(Some(id)).join(NetworkMonitor::DB_FILE);
|
||||
NetworkMonitor::default_credentials_database_path(id);
|
||||
self.coconut_signer.dkg_persistent_state_path =
|
||||
Config::default_data_directory(Some(id)).join(CoconutSigner::DKG_PERSISTENT_STATE_FILE);
|
||||
self.coconut_signer.verification_key_path = Config::default_data_directory(Some(id))
|
||||
.join(CoconutSigner::COCONUT_VERIFICATION_KEY_FILE);
|
||||
self.coconut_signer.secret_key_path =
|
||||
Config::default_data_directory(Some(id)).join(CoconutSigner::COCONUT_SECRET_KEY_FILE);
|
||||
CoconutSigner::default_dkg_persistent_state_path(id);
|
||||
self.coconut_signer.verification_key_path =
|
||||
CoconutSigner::default_coconut_verification_key_path(id);
|
||||
self.coconut_signer.secret_key_path = CoconutSigner::default_coconut_secret_key_path(id);
|
||||
self.coconut_signer.decryption_key_path =
|
||||
Config::default_data_directory(Some(id)).join(CoconutSigner::DKG_DECRYPTION_KEY_FILE);
|
||||
self.coconut_signer.public_key_with_proof_path = Config::default_data_directory(Some(id))
|
||||
.join(CoconutSigner::DKG_PUBLIC_KEY_WITH_PROOF_FILE);
|
||||
CoconutSigner::default_dkg_decryption_key_path(id);
|
||||
self.coconut_signer.public_key_with_proof_path =
|
||||
CoconutSigner::default_dkg_public_key_with_proof_path(id);
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Nym Connect</title>
|
||||
</head>
|
||||
<body style="background: rgb(29, 33, 37);">
|
||||
<div id="root-growth"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Nym Wallet Logs</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root-log"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -41,8 +41,8 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_repr = "0.1"
|
||||
tap = "1.0.1"
|
||||
tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open", "system-tray", "window-close", "window-minimize", "window-start-dragging"] }
|
||||
# tauri = { version = "2.0.0-alpha.0", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open", "system-tray", "window-close", "window-minimize", "window-start-dragging"] }
|
||||
# TODO swithing to `rfd101` temporarily, untill https://github.com/tauri-apps/tauri/pull/6174 is merged
|
||||
tauri = { git = "https://github.com/tauri-apps/tauri", branch = "rfd101", features = ["clipboard-write-text", "native-tls-vendored", "notification-all", "shell-open", "system-tray", "window-close", "window-minimize", "window-start-dragging"] }
|
||||
tendermint-rpc = "0.23.0"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.24.1", features = ["sync", "time"] }
|
||||
@@ -64,3 +64,9 @@ tempfile = "3.3.0"
|
||||
[features]
|
||||
default = ["custom-protocol"]
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
|
||||
# [profile.dev]
|
||||
# strip = true
|
||||
# opt-level = "s"
|
||||
# lto = true
|
||||
|
||||
|
||||
+91
-90
@@ -1,111 +1,112 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("rustPlugin")
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("rustPlugin")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 33
|
||||
defaultConfig {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
||||
applicationId = "net.nymtech.nym_connect_android"
|
||||
minSdk = 24
|
||||
targetSdk = 33
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
compileSdk = 33
|
||||
defaultConfig {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
||||
applicationId = "net.nymtech.nym_connect_android"
|
||||
minSdk = 24
|
||||
targetSdk = 33
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
}
|
||||
sourceSets.getByName("main") {
|
||||
// Vulkan validation layers
|
||||
val ndkHome = System.getenv("NDK_HOME")
|
||||
jniLibs.srcDir("${ndkHome}/sources/third_party/vulkan/src/build-android/jniLibs")
|
||||
}
|
||||
buildTypes {
|
||||
getByName("debug") {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "true"
|
||||
isDebuggable = true
|
||||
isJniDebuggable = true
|
||||
isMinifyEnabled = false
|
||||
packagingOptions {
|
||||
jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so")
|
||||
|
||||
jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so")
|
||||
|
||||
jniLibs.keepDebugSymbols.add("*/x86/*.so")
|
||||
|
||||
jniLibs.keepDebugSymbols.add("*/x86_64/*.so")
|
||||
}
|
||||
}
|
||||
sourceSets.getByName("main") {
|
||||
// Vulkan validation layers
|
||||
val ndkHome = System.getenv("NDK_HOME")
|
||||
jniLibs.srcDir("${ndkHome}/sources/third_party/vulkan/src/build-android/jniLibs")
|
||||
getByName("release") {
|
||||
isMinifyEnabled = false
|
||||
// proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
|
||||
}
|
||||
buildTypes {
|
||||
getByName("debug") {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "true"
|
||||
isDebuggable = true
|
||||
isJniDebuggable = true
|
||||
isMinifyEnabled = false
|
||||
packagingOptions {
|
||||
jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so")
|
||||
}
|
||||
flavorDimensions.add("abi")
|
||||
productFlavors {
|
||||
create("universal") {
|
||||
val abiList = findProperty("abiList") as? String
|
||||
|
||||
jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so")
|
||||
|
||||
jniLibs.keepDebugSymbols.add("*/x86/*.so")
|
||||
|
||||
jniLibs.keepDebugSymbols.add("*/x86_64/*.so")
|
||||
}
|
||||
}
|
||||
getByName("release") {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
|
||||
}
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += abiList?.split(",")?.map { it.trim() } ?: listOf(
|
||||
"arm64-v8a", "armeabi-v7a", "x86", "x86_64",
|
||||
)
|
||||
}
|
||||
}
|
||||
flavorDimensions.add("abi")
|
||||
productFlavors {
|
||||
create("universal") {
|
||||
val abiList = findProperty("abiList") as? String
|
||||
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += abiList?.split(",")?.map { it.trim() } ?: listOf( "arm64-v8a", "armeabi-v7a", "x86", "x86_64",
|
||||
)
|
||||
}
|
||||
}
|
||||
create("arm64") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += listOf("arm64-v8a")
|
||||
}
|
||||
}
|
||||
|
||||
create("arm") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += listOf("armeabi-v7a")
|
||||
}
|
||||
}
|
||||
|
||||
create("x86") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += listOf("x86")
|
||||
}
|
||||
}
|
||||
|
||||
create("x86_64") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += listOf("x86_64")
|
||||
}
|
||||
}
|
||||
create("arm64") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += listOf("arm64-v8a")
|
||||
}
|
||||
}
|
||||
|
||||
assetPacks += mutableSetOf()
|
||||
namespace = "net.nymtech.nym_connect_android"
|
||||
create("arm") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += listOf("armeabi-v7a")
|
||||
}
|
||||
}
|
||||
|
||||
create("x86") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += listOf("x86")
|
||||
}
|
||||
}
|
||||
|
||||
create("x86_64") {
|
||||
dimension = "abi"
|
||||
ndk {
|
||||
abiFilters += listOf("x86_64")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assetPacks += mutableSetOf()
|
||||
namespace = "net.nymtech.nym_connect_android"
|
||||
}
|
||||
|
||||
rust {
|
||||
rootDirRel = "../../../../"
|
||||
targets = listOf("aarch64", "armv7", "i686", "x86_64")
|
||||
arches = listOf("arm64", "arm", "x86", "x86_64")
|
||||
rootDirRel = "../../../../"
|
||||
targets = listOf("aarch64", "armv7", "i686", "x86_64")
|
||||
arches = listOf("arm64", "arm", "x86", "x86_64")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.webkit:webkit:1.5.0")
|
||||
implementation("androidx.appcompat:appcompat:1.5.1")
|
||||
implementation("com.google.android.material:material:1.7.0")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.4")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
|
||||
implementation("androidx.webkit:webkit:1.5.0")
|
||||
implementation("androidx.appcompat:appcompat:1.5.1")
|
||||
implementation("com.google.android.material:material:1.7.0")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.4")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
android.applicationVariants.all {
|
||||
tasks["mergeUniversalReleaseJniLibFolders"].dependsOn(tasks["rustBuildRelease"])
|
||||
tasks["mergeUniversalDebugJniLibFolders"].dependsOn(tasks["rustBuildDebug"])
|
||||
productFlavors.filter{ it.name != "universal" }.forEach { _ ->
|
||||
val archAndBuildType = name.capitalize()
|
||||
tasks["merge${archAndBuildType}JniLibFolders"].dependsOn(tasks["rustBuild${archAndBuildType}"])
|
||||
}
|
||||
android.applicationVariants.all {
|
||||
tasks["mergeUniversalReleaseJniLibFolders"].dependsOn(tasks["rustBuildRelease"])
|
||||
tasks["mergeUniversalDebugJniLibFolders"].dependsOn(tasks["rustBuildDebug"])
|
||||
productFlavors.filter { it.name != "universal" }.forEach { _ ->
|
||||
val archAndBuildType = name.capitalize()
|
||||
tasks["merge${archAndBuildType}JniLibFolders"].dependsOn(tasks["rustBuild${archAndBuildType}"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
/home/pierre/Documents/nym/nym/nym-connect-android/src-tauri/target/aarch64-linux-android/debug/libnym_connect_android.so
|
||||
+1
@@ -0,0 +1 @@
|
||||
/home/pierre/Documents/nym/nym/nym-connect-android/src-tauri/target/armv7-linux-androideabi/debug/libnym_connect_android.so
|
||||
+1
@@ -0,0 +1 @@
|
||||
/home/pierre/Documents/nym/nym/nym-connect-android/src-tauri/target/i686-linux-android/debug/libnym_connect_android.so
|
||||
+6
-6
@@ -2,15 +2,15 @@
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.nym_connect_android" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_200</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/black</item>
|
||||
<item name="colorPrimary">@color/grey_800</item>
|
||||
<item name="colorPrimaryVariant">@color/grey_900</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||
<item name="colorSecondary">@color/green_500</item>
|
||||
<item name="colorSecondaryVariant">@color/green_900</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
</resources>
|
||||
+4
-5
@@ -1,10 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="grey_900">#212121</color>
|
||||
<color name="grey_800">#424242</color>
|
||||
<color name="green_500">#4caf50</color>
|
||||
<color name="green_900">#1b5e20</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">nym-connect-android</string>
|
||||
<string name="app_name">Nym Connect</string>
|
||||
</resources>
|
||||
+4
-4
@@ -2,12 +2,12 @@
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.nym_connect_android" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_500</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorPrimary">@color/grey_800</item>
|
||||
<item name="colorPrimaryVariant">@color/grey_900</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||
<item name="colorSecondary">@color/green_500</item>
|
||||
<item name="colorSecondaryVariant">@color/green_900</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
|
||||
@@ -40,11 +40,6 @@ pub enum BackendError {
|
||||
#[from]
|
||||
source: ClientCoreError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
ApiClientError {
|
||||
#[from]
|
||||
source: crate::operations::growth::api_client::ApiClientError,
|
||||
},
|
||||
|
||||
#[error("could not send disconnect signal to the SOCKS5 client")]
|
||||
CoundNotSendDisconnectSignal,
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ApiClientError {
|
||||
#[error("{source}")]
|
||||
Reqwest {
|
||||
#[from]
|
||||
source: reqwest::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
SerdeJson {
|
||||
#[from]
|
||||
source: serde_json::Error,
|
||||
},
|
||||
#[error("{0}")]
|
||||
Status(String),
|
||||
}
|
||||
|
||||
const API_BASE_URL: &str = "https://growth-api.nymtech.net";
|
||||
|
||||
// For development mode, switch to this
|
||||
// const API_BASE_URL: &str = "http://localhost:8000";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GrowthApiClient {
|
||||
base_url: String,
|
||||
}
|
||||
|
||||
impl GrowthApiClient {
|
||||
pub fn new(resource_base: &str) -> Self {
|
||||
let base_url = std::env::var("API_BASE_URL").unwrap_or_else(|_| API_BASE_URL.to_string());
|
||||
GrowthApiClient {
|
||||
base_url: format!("{base_url}{resource_base}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn registrations() -> Registrations {
|
||||
Registrations::new(GrowthApiClient::new("/v1/tne"))
|
||||
}
|
||||
|
||||
pub fn daily_draws() -> DailyDraws {
|
||||
DailyDraws::new(GrowthApiClient::new("/v1/tne/daily_draw"))
|
||||
}
|
||||
|
||||
pub(crate) async fn get<T: DeserializeOwned>(&self, url: &str) -> Result<T, ApiClientError> {
|
||||
log::info!(">>> GET {}", url);
|
||||
let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:1080")?;
|
||||
let client = reqwest::Client::builder()
|
||||
.proxy(proxy)
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.build()?;
|
||||
|
||||
match client.get(format!("{}{}", self.base_url, url)).send().await {
|
||||
Ok(res) => {
|
||||
if res.status().is_client_error() || res.status().is_server_error() {
|
||||
log::error!("<<< {}", res.status());
|
||||
return Err(ApiClientError::Status(res.status().to_string()));
|
||||
}
|
||||
match res.text().await {
|
||||
Ok(response_body) => {
|
||||
log::info!("<<< {}", response_body);
|
||||
match serde_json::from_str(&response_body) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => {
|
||||
log::error!("<<< JSON parsing error: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("<<< Request error: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("<<< Response parsing error: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: use the method in `operations::http` instead
|
||||
pub(crate) async fn post<REQ: Serialize + ?Sized, RESP: DeserializeOwned>(
|
||||
&self,
|
||||
url: &str,
|
||||
body: &REQ,
|
||||
) -> Result<RESP, ApiClientError> {
|
||||
log::info!(">>> POST {}", url);
|
||||
let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:1080")?;
|
||||
let client = reqwest::Client::builder()
|
||||
.proxy(proxy)
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.build()?;
|
||||
|
||||
match client
|
||||
.post(format!("{}{}", self.base_url, url))
|
||||
.json(body)
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(res) => {
|
||||
if res.status().is_client_error() || res.status().is_server_error() {
|
||||
log::error!("<<< {}", res.status());
|
||||
return Err(ApiClientError::Status(res.status().to_string()));
|
||||
}
|
||||
match res.text().await {
|
||||
Ok(response_body) => {
|
||||
log::info!("<<< {}", response_body);
|
||||
match serde_json::from_str(&response_body) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => {
|
||||
log::error!("<<< JSON parsing error: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("<<< Request error: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("<<< Response parsing error: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ClientIdPartial {
|
||||
pub client_id: String,
|
||||
pub client_id_signature: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Registration {
|
||||
pub id: String,
|
||||
pub client_id: String,
|
||||
pub timestamp: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Ping {
|
||||
pub client_id: String,
|
||||
pub timestamp: String,
|
||||
}
|
||||
|
||||
pub struct Registrations {
|
||||
client: GrowthApiClient,
|
||||
}
|
||||
|
||||
impl Registrations {
|
||||
pub fn new(client: GrowthApiClient) -> Self {
|
||||
Registrations { client }
|
||||
}
|
||||
|
||||
pub async fn register(
|
||||
&self,
|
||||
registration: &ClientIdPartial,
|
||||
) -> Result<Registration, ApiClientError> {
|
||||
self.client.post("/register", ®istration).await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn unregister(&self, registration: &ClientIdPartial) -> Result<(), ApiClientError> {
|
||||
self.client.post("/unregister", ®istration).await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn status(&self, registration: &ClientIdPartial) -> Result<(), ApiClientError> {
|
||||
self.client.post("/status", ®istration).await
|
||||
}
|
||||
|
||||
pub async fn ping(&self, registration: &ClientIdPartial) -> Result<(), ApiClientError> {
|
||||
self.client.post("/ping", ®istration).await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn health(&self) -> Result<(), ApiClientError> {
|
||||
self.client.get("/health").await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct DrawEntryPartial {
|
||||
pub draw_id: String,
|
||||
pub client_id: String,
|
||||
pub client_id_signature: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct DrawEntry {
|
||||
pub id: String,
|
||||
pub draw_id: String,
|
||||
pub timestamp: String,
|
||||
pub status: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct DrawWithWordOfTheDay {
|
||||
pub id: String,
|
||||
pub start_utc: String,
|
||||
pub end_utc: String,
|
||||
pub word_of_the_day: Option<String>,
|
||||
pub last_modified: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ClaimPartial {
|
||||
pub draw_id: String,
|
||||
pub registration_id: String,
|
||||
pub client_id: String,
|
||||
pub client_id_signature: String,
|
||||
pub wallet_address: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Winner {
|
||||
pub id: String,
|
||||
pub client_id: String,
|
||||
pub draw_id: String,
|
||||
pub timestamp: String,
|
||||
pub winner_reg_id: String,
|
||||
pub winner_wallet_address: Option<String>,
|
||||
pub winner_claim_timestamp: Option<String>,
|
||||
}
|
||||
|
||||
pub struct DailyDraws {
|
||||
client: GrowthApiClient,
|
||||
}
|
||||
|
||||
impl DailyDraws {
|
||||
pub fn new(client: GrowthApiClient) -> Self {
|
||||
DailyDraws { client }
|
||||
}
|
||||
|
||||
pub async fn current(&self) -> Result<DrawWithWordOfTheDay, ApiClientError> {
|
||||
self.client.get("/current").await
|
||||
}
|
||||
|
||||
pub async fn next(&self) -> Result<DrawWithWordOfTheDay, ApiClientError> {
|
||||
self.client.get("/next").await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn status(&self, draw_id: &str) -> Result<DrawWithWordOfTheDay, ApiClientError> {
|
||||
self.client.get(format!("/status/{draw_id}").as_str()).await
|
||||
}
|
||||
|
||||
pub async fn enter(&self, entry: &DrawEntryPartial) -> Result<DrawEntry, ApiClientError> {
|
||||
self.client.post("/enter", entry).await
|
||||
}
|
||||
|
||||
pub async fn entries(
|
||||
&self,
|
||||
client_id: &ClientIdPartial,
|
||||
) -> Result<Vec<DrawEntry>, ApiClientError> {
|
||||
self.client.post("/entries", client_id).await
|
||||
}
|
||||
|
||||
pub async fn claim(&self, claim: &ClaimPartial) -> Result<Winner, ApiClientError> {
|
||||
self.client.post("/claim", claim).await
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
use rust_embed::RustEmbed;
|
||||
extern crate yaml_rust;
|
||||
use yaml_rust::YamlLoader;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "../src/components/Growth/content/"]
|
||||
#[include = "*.yaml"]
|
||||
#[exclude = "*.mdx"]
|
||||
struct Asset;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NotificationContent {
|
||||
pub title: String,
|
||||
pub body: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Notifications {
|
||||
pub you_are_in_draw: NotificationContent,
|
||||
pub take_part: NotificationContent,
|
||||
}
|
||||
|
||||
pub struct Content {}
|
||||
|
||||
const RESOURCE_ERROR: &str = "❌ RESOURCE ERROR";
|
||||
|
||||
fn get_as_string_or_error_message(value: &yaml_rust::Yaml) -> String {
|
||||
value.as_str().unwrap_or(RESOURCE_ERROR).to_string()
|
||||
}
|
||||
|
||||
impl Content {
|
||||
pub fn get_notifications() -> Notifications {
|
||||
let content = Asset::get("en.yaml").unwrap();
|
||||
let s = std::str::from_utf8(content.data.as_ref()).unwrap();
|
||||
let content = YamlLoader::load_from_str(s).unwrap();
|
||||
let content = &content[0];
|
||||
|
||||
Notifications {
|
||||
you_are_in_draw: NotificationContent {
|
||||
title: get_as_string_or_error_message(
|
||||
&content["testAndEarn"]["notifications"]["youAreInDraw"]["title"],
|
||||
),
|
||||
body: get_as_string_or_error_message(
|
||||
&content["testAndEarn"]["notifications"]["youAreInDraw"]["body"],
|
||||
),
|
||||
},
|
||||
take_part: NotificationContent {
|
||||
title: get_as_string_or_error_message(
|
||||
&content["testAndEarn"]["notifications"]["takePart"]["title"],
|
||||
),
|
||||
body: get_as_string_or_error_message(
|
||||
&content["testAndEarn"]["notifications"]["takePart"]["body"],
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod api_client;
|
||||
pub mod assets;
|
||||
pub mod test_and_earn;
|
||||
@@ -1,159 +0,0 @@
|
||||
use crate::error::BackendError;
|
||||
use crate::operations::export::get_identity_key;
|
||||
use crate::operations::growth::api_client::{
|
||||
ClaimPartial, ClientIdPartial, DrawEntry, DrawEntryPartial, DrawWithWordOfTheDay,
|
||||
GrowthApiClient, Registration, Winner,
|
||||
};
|
||||
use crate::State;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
#[cfg(desktop)]
|
||||
use tauri::api::notification::Notification;
|
||||
use tauri::Manager;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
async fn get_client_id(
|
||||
state: &tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<ClientIdPartial, BackendError> {
|
||||
let keypair = get_identity_key(state).await?;
|
||||
let client_id = keypair.public_key().to_base58_string();
|
||||
let client_id_signature = keypair
|
||||
.private_key()
|
||||
.sign(client_id.as_bytes())
|
||||
.to_base58_string();
|
||||
Ok(ClientIdPartial {
|
||||
client_id,
|
||||
client_id_signature,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_get_client_id(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<ClientIdPartial, BackendError> {
|
||||
get_client_id(&state).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_take_part(
|
||||
app_handle: tauri::AppHandle,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Registration, BackendError> {
|
||||
let notifications = super::assets::Content::get_notifications();
|
||||
|
||||
let client_id = get_client_id(&state).await?;
|
||||
let registration = GrowthApiClient::registrations()
|
||||
.register(&client_id)
|
||||
.await?;
|
||||
|
||||
log::info!("<<< Test&Earn: registration details: {:?}", registration);
|
||||
|
||||
#[cfg(desktop)]
|
||||
if let Err(e) = Notification::new(&app_handle.config().tauri.bundle.identifier)
|
||||
.title(notifications.take_part.title)
|
||||
.body(notifications.take_part.body)
|
||||
.show()
|
||||
{
|
||||
log::error!("Could not show notification. Error = {:?}", e);
|
||||
}
|
||||
|
||||
Ok(registration)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Draws {
|
||||
pub current: Option<DrawWithWordOfTheDay>,
|
||||
pub next: Option<DrawWithWordOfTheDay>,
|
||||
pub draws: Vec<DrawEntry>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_get_draws(client_details: ClientIdPartial) -> Result<Draws, BackendError> {
|
||||
let draws_api = GrowthApiClient::daily_draws();
|
||||
|
||||
let current = draws_api.current().await.ok();
|
||||
let next = draws_api.next().await.ok();
|
||||
let draws = draws_api.entries(&client_details).await?;
|
||||
|
||||
Ok(Draws {
|
||||
current,
|
||||
next,
|
||||
draws,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_enter_draw(
|
||||
client_details: ClientIdPartial,
|
||||
draw_id: String,
|
||||
) -> Result<DrawEntry, BackendError> {
|
||||
Ok(GrowthApiClient::daily_draws()
|
||||
.enter(&DrawEntryPartial {
|
||||
draw_id,
|
||||
client_id: client_details.client_id,
|
||||
client_id_signature: client_details.client_id_signature,
|
||||
})
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_submit_wallet_address(
|
||||
client_details: ClientIdPartial,
|
||||
draw_id: String,
|
||||
wallet_address: String,
|
||||
registration_id: String,
|
||||
) -> Result<Winner, BackendError> {
|
||||
Ok(GrowthApiClient::daily_draws()
|
||||
.claim(&ClaimPartial {
|
||||
draw_id,
|
||||
client_id: client_details.client_id,
|
||||
client_id_signature: client_details.client_id_signature,
|
||||
wallet_address,
|
||||
registration_id,
|
||||
})
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_ping(client_details: ClientIdPartial) -> Result<(), BackendError> {
|
||||
log::info!("Test&Earn is sending a ping...");
|
||||
Ok(GrowthApiClient::registrations()
|
||||
.ping(&client_details)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_toggle_window(
|
||||
app_handle: tauri::AppHandle,
|
||||
window_title: Option<String>,
|
||||
) -> Result<(), BackendError> {
|
||||
if let Some(window) = app_handle.windows().get("growth") {
|
||||
log::info!("Closing growth window...");
|
||||
if let Err(e) = window.close() {
|
||||
log::error!("Unable to close growth window: {:?}", e);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
log::info!("Creating growth window...");
|
||||
match tauri::WindowBuilder::new(
|
||||
&app_handle,
|
||||
"growth",
|
||||
tauri::WindowUrl::App("growth.html".into()),
|
||||
)
|
||||
.title(window_title.unwrap_or_else(|| "NymConnect Test&Earn".to_string()))
|
||||
.build()
|
||||
{
|
||||
Ok(window) => {
|
||||
if let Err(e) = window.set_focus() {
|
||||
log::error!("Unable to focus growth window: {:?}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Unable to create growth window: {:?}", e);
|
||||
Err(BackendError::NewWindowError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
pub mod connection;
|
||||
pub mod directory;
|
||||
pub mod export;
|
||||
pub mod growth;
|
||||
pub mod help;
|
||||
pub mod http;
|
||||
#[cfg(desktop)]
|
||||
|
||||
@@ -161,7 +161,7 @@ impl State {
|
||||
|
||||
pub fn load_socks5_config(&self) -> Result<Socks5Config> {
|
||||
let id = self.get_config_id()?;
|
||||
let config = Socks5Config::load_from_file(Some(&id))
|
||||
let config = Socks5Config::load_from_file(&id)
|
||||
.tap_err(|_| log::warn!("Failed to load configuration file"))?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Badge, Box, Button, Tooltip } from '@mui/material';
|
||||
import MonetizationOnIcon from '@mui/icons-material/MonetizationOn';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import Content from './content/en.yaml';
|
||||
import { useClientContext } from '../../context/main';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
import { NymShipyardTheme } from '../../theme';
|
||||
import { ConnectionStatusKind } from '../../types';
|
||||
|
||||
export const Wrapper: FCWithChildren<{ disabled: boolean }> = ({ disabled, children }) => {
|
||||
if (disabled) {
|
||||
return (
|
||||
<Badge badgeContent="!" color="warning">
|
||||
<Tooltip arrow title={disabled ? Content.testAndEarn.mainWindow.button.popup.disconnected : undefined}>
|
||||
<div>{children}</div>
|
||||
</Tooltip>
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export const TestAndEarnButtonArea: FCWithChildren = () => {
|
||||
const clientContext = useClientContext();
|
||||
const context = useTestAndEarnContext();
|
||||
const disabled = clientContext.connectionStatus !== ConnectionStatusKind.connected;
|
||||
const pinger = React.useRef<NodeJS.Timer | null>();
|
||||
|
||||
const doPing = async () => {
|
||||
if (context.clientDetails) {
|
||||
try {
|
||||
await invoke('growth_tne_ping', { clientDetails: context.clientDetails });
|
||||
} catch (_e) {
|
||||
// console.error('Failed to ping: ', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
if (!disabled) {
|
||||
// sleep a little until the SOCKS5 proxy connects
|
||||
setTimeout(() => {
|
||||
doPing();
|
||||
}, 1000 * 10);
|
||||
|
||||
// update every 15 mins
|
||||
pinger.current = setInterval(doPing, 1000 * 60 * 15);
|
||||
} else if (pinger.current) {
|
||||
clearInterval(pinger.current);
|
||||
pinger.current = null;
|
||||
}
|
||||
})();
|
||||
}, [disabled, context.clientDetails]);
|
||||
|
||||
const handleClick = async () => {
|
||||
if (!disabled) {
|
||||
await context.toggleGrowthWindow(Content.testAndEarn.popupWindow.title);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<NymShipyardTheme>
|
||||
<Box justifyContent="center" display="grid">
|
||||
<Wrapper disabled={disabled}>
|
||||
<Button
|
||||
color={disabled ? 'secondary' : undefined}
|
||||
variant="contained"
|
||||
size="small"
|
||||
endIcon={<MonetizationOnIcon />}
|
||||
sx={{ width: '150px', mb: 4, opacity: disabled ? 0.4 : undefined }}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{context.registration
|
||||
? Content.testAndEarn.mainWindow.button.text.entered
|
||||
: Content.testAndEarn.mainWindow.button.text.default}
|
||||
</Button>
|
||||
</Wrapper>
|
||||
</Box>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
};
|
||||
@@ -1,94 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { DateTime, Duration } from 'luxon';
|
||||
import {
|
||||
TestAndEarnCurrentDraw,
|
||||
TestAndEarnCurrentDrawEntered,
|
||||
TestAndEarnCurrentDrawFuture,
|
||||
} from './TestAndEarnCurrentDraw';
|
||||
import { NymShipyardTheme } from '../../theme';
|
||||
import { DrawEntryStatus } from './context/types';
|
||||
import { testMarkdown } from './context/mocks/TestAndEarnContext';
|
||||
|
||||
export default {
|
||||
title: 'Growth/TestAndEarn/Components/Cards/Current Draw',
|
||||
component: TestAndEarnCurrentDraw,
|
||||
} as ComponentMeta<typeof TestAndEarnCurrentDraw>;
|
||||
|
||||
export const Valid = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnCurrentDraw
|
||||
draw={{
|
||||
id: '1',
|
||||
start_utc: DateTime.now().toISO(),
|
||||
end_utc: DateTime.now()
|
||||
.plus(Duration.fromMillis(1000 * 3600))
|
||||
.toISO(),
|
||||
last_modified: DateTime.now().toISO(),
|
||||
word_of_the_day: 'words words words',
|
||||
}}
|
||||
/>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
|
||||
export const EnteredMalformedDraw = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnCurrentDrawEntered
|
||||
draw={{
|
||||
id: '1',
|
||||
start_utc: DateTime.now().toISO(),
|
||||
end_utc: DateTime.now()
|
||||
.plus(Duration.fromMillis(1000 * 3600))
|
||||
.toISO(),
|
||||
last_modified: DateTime.now().toISO(),
|
||||
word_of_the_day: undefined,
|
||||
entry: {
|
||||
draw_id: '1',
|
||||
status: DrawEntryStatus.pending,
|
||||
id: 'aaaa',
|
||||
timestamp: DateTime.now().toISO(),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
|
||||
export const EnteredDraw = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnCurrentDrawEntered
|
||||
draw={{
|
||||
id: '1',
|
||||
start_utc: DateTime.now().toISO(),
|
||||
end_utc: DateTime.now()
|
||||
.plus(Duration.fromMillis(1000 * 3600))
|
||||
.toISO(),
|
||||
last_modified: DateTime.now().toISO(),
|
||||
word_of_the_day: testMarkdown,
|
||||
entry: {
|
||||
draw_id: '1',
|
||||
status: DrawEntryStatus.pending,
|
||||
id: 'aaaa',
|
||||
timestamp: DateTime.now().toISO(),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
|
||||
export const Future = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnCurrentDrawFuture
|
||||
draw={{
|
||||
id: '1',
|
||||
start_utc: DateTime.now()
|
||||
.plus(Duration.fromMillis(1000 * 3600))
|
||||
.toISO(),
|
||||
end_utc: DateTime.now()
|
||||
.plus(Duration.fromMillis(1000 * 3600 * 2))
|
||||
.toISO(),
|
||||
last_modified: DateTime.now().toISO(),
|
||||
word_of_the_day: 'words words words',
|
||||
}}
|
||||
/>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
@@ -1,192 +0,0 @@
|
||||
import React from 'react';
|
||||
import LoadingButton from '@mui/lab/LoadingButton';
|
||||
import { Alert, AlertTitle, Box, Card, CardContent, CardMedia, Link, Typography } from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { DateTime } from 'luxon';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import assetAnimation from './content/assets/matrix.webp';
|
||||
import { CopyToClipboard } from '../CopyToClipboard';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
import { DrawEntryStatus, DrawWithWordOfTheDay } from './context/types';
|
||||
import Content from './content/en.yaml';
|
||||
|
||||
export const TestAndEarnCurrentDrawFuture: FCWithChildren<{ draw?: DrawWithWordOfTheDay }> = ({ draw }) => {
|
||||
const startsUtc = React.useMemo(() => draw && DateTime.fromISO(draw.start_utc), [draw?.start_utc]);
|
||||
const startsIn = React.useMemo(() => {
|
||||
if (draw && startsUtc) {
|
||||
return startsUtc.toRelative();
|
||||
}
|
||||
return undefined;
|
||||
}, [draw?.start_utc]);
|
||||
|
||||
if (!draw || !startsUtc) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Card sx={{ mb: 2 }} elevation={10}>
|
||||
<CardContent>
|
||||
<h3>
|
||||
{Content.testAndEarn.draw.next.header} {startsIn} ⏰
|
||||
</h3>
|
||||
<p>on {startsUtc.toLocaleString(DateTime.DATETIME_FULL)}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export const TestAndEarnCurrentDrawEnter: FCWithChildren<{ draw?: DrawWithWordOfTheDay }> = ({ draw }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
const [error, setError] = React.useState<string>();
|
||||
const handleClick = async () => {
|
||||
if (!draw) {
|
||||
setError('No draw selected');
|
||||
return;
|
||||
}
|
||||
|
||||
setBusy(true);
|
||||
try {
|
||||
await context.enterDraw(draw.id);
|
||||
} catch (e) {
|
||||
const message = `${e}`;
|
||||
console.error('Could not enter draw', message);
|
||||
setError(message);
|
||||
}
|
||||
setBusy(false);
|
||||
};
|
||||
return (
|
||||
<Box display="flex" flexDirection="column" alignItems="center" py={3} px={2} mx={6} my={2}>
|
||||
<Typography mb={4}>Complete today’s task for the chance to earn 1000 NYMs.</Typography>
|
||||
<LoadingButton variant="contained" size="large" loading={busy} onClick={handleClick}>
|
||||
Start task ✨
|
||||
</LoadingButton>
|
||||
{error && (
|
||||
<Box mt={2}>
|
||||
<Alert variant="filled" severity="error">
|
||||
<AlertTitle>Oh no! Something went wrong.</AlertTitle>
|
||||
{error}
|
||||
</Alert>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const TestAndEarnCurrentDrawEntered: FCWithChildren<{ draw?: DrawWithWordOfTheDay }> = ({ draw }) => {
|
||||
if (!draw || !draw.entry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!draw.word_of_the_day) {
|
||||
return (
|
||||
<Alert severity="error" variant="filled">
|
||||
<AlertTitle>Oh no! Something is wrong</AlertTitle>
|
||||
Someone configured the wrong instructions for the task, you will not be able to see it until this is fixed
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
alignItems="center"
|
||||
sx={{ background: 'rgba(255,255,255,0.1)' }}
|
||||
py={4}
|
||||
mx={6}
|
||||
my={2}
|
||||
borderRadius={2}
|
||||
>
|
||||
<Box py={2} px={4} color="warning.light">
|
||||
<ReactMarkdown>{draw.word_of_the_day}</ReactMarkdown>
|
||||
</Box>
|
||||
|
||||
<Typography>{Content.testAndEarn.task.afterText}</Typography>
|
||||
<Typography mt={2} fontFamily="monospace" fontWeight="bold" color="warning.main">
|
||||
{draw.entry.id} <CopyToClipboard iconButton light text={draw.entry.id} />
|
||||
</Typography>
|
||||
|
||||
<Typography mt={2}>{Content.testAndEarn.task.beforeSocials}</Typography>
|
||||
<Typography mt={2} mx={1} textAlign="center">
|
||||
<Typography component="span" color="info.light" fontWeight="bold">
|
||||
Twitter
|
||||
</Typography>{' '}
|
||||
- remember to
|
||||
<Typography component="span" color="info.light">
|
||||
@nymproject
|
||||
</Typography>{' '}
|
||||
and use the hashtag{' '}
|
||||
<Typography component="span" color="info.light">
|
||||
#PrivacyLovesCompany
|
||||
</Typography>
|
||||
</Typography>
|
||||
<Typography mt={2}>or</Typography>
|
||||
<Typography textAlign="center" fontWeight="bold">
|
||||
Nym{' '}
|
||||
<Link target="_blank" href="https://t.me/nymchan" color="info.light">
|
||||
Telegram channel
|
||||
</Link>
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const TestAndEarnCurrentDraw: FCWithChildren<{
|
||||
draw?: DrawWithWordOfTheDay;
|
||||
sx?: SxProps;
|
||||
}> = ({ draw }) => {
|
||||
const [trigger, setTrigger] = React.useState(DateTime.now().toISO());
|
||||
const endsUtc = React.useMemo(() => draw && DateTime.fromISO(draw.end_utc), [draw?.end_utc]);
|
||||
const closesIn = React.useMemo(() => {
|
||||
if (draw && endsUtc) {
|
||||
return endsUtc.toRelative();
|
||||
}
|
||||
return undefined;
|
||||
}, [trigger, endsUtc]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const timer = setInterval(() => setTrigger(DateTime.now().toISO()), 1000 * 3600 * 15);
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
if (draw && closesIn && endsUtc) {
|
||||
return (
|
||||
<Card elevation={10}>
|
||||
<CardContent>
|
||||
<h3>
|
||||
{"Today's task ends "}
|
||||
{closesIn}
|
||||
<Typography sx={{ opacity: 0.5 }}>
|
||||
{endsUtc.weekdayLong} {endsUtc.toLocaleString(DateTime.DATETIME_FULL)}
|
||||
</Typography>
|
||||
</h3>
|
||||
{!draw.entry && <TestAndEarnCurrentDrawEnter draw={draw} />}
|
||||
{draw.entry && <TestAndEarnCurrentDrawEntered draw={draw} />}
|
||||
</CardContent>
|
||||
<CardMedia component="img" height="150" image={assetAnimation} alt="lottery" />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const TestAndEarnCurrentDrawWithState: FCWithChildren<{
|
||||
sx?: SxProps;
|
||||
}> = ({ sx }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
|
||||
if (
|
||||
context.draws?.current?.entry?.status === DrawEntryStatus.winner ||
|
||||
context.draws?.current?.entry?.status === DrawEntryStatus.claimed ||
|
||||
context.draws?.current?.entry?.status === DrawEntryStatus.noWin
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!context.draws?.current) {
|
||||
return <TestAndEarnCurrentDrawFuture draw={context.draws?.next} />;
|
||||
}
|
||||
|
||||
return <TestAndEarnCurrentDraw sx={sx} draw={context.draws.current} />;
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
/* eslint-disable react/jsx-pascal-case */
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { NymShipyardTheme } from 'src/theme';
|
||||
import { TestAndEarnDraws } from './TestAndEarnDraws';
|
||||
import { MockTestAndEarnProvider_RegisteredWithAllDraws } from './context/mocks/TestAndEarnContext';
|
||||
|
||||
export default {
|
||||
title: 'Growth/TestAndEarn/Components/Cards/Draws',
|
||||
component: TestAndEarnDraws,
|
||||
} as ComponentMeta<typeof TestAndEarnDraws>;
|
||||
|
||||
export const Draws = () => (
|
||||
<NymShipyardTheme>
|
||||
<MockTestAndEarnProvider_RegisteredWithAllDraws>
|
||||
<TestAndEarnDraws />
|
||||
</MockTestAndEarnProvider_RegisteredWithAllDraws>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
@@ -1,196 +0,0 @@
|
||||
/* eslint-disable react/jsx-no-useless-fragment */
|
||||
import React from 'react';
|
||||
import LoadingButton from '@mui/lab/LoadingButton';
|
||||
import {
|
||||
Alert,
|
||||
AlertTitle,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
Chip,
|
||||
Dialog,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableRow,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
import { DrawEntry, DrawEntryStatus } from './context/types';
|
||||
import { CopyToClipboard } from '../CopyToClipboard';
|
||||
import { TestAndEarnEnterWalletAddress } from './TestAndEarnEnterWalletAddress';
|
||||
import Content from './content/en.yaml';
|
||||
|
||||
const statusToText = (status: string): string => Content.testAndEarn.status.chip[status] || '-';
|
||||
|
||||
const statusToColor = (status: string): 'info' | 'success' | 'warning' | undefined => {
|
||||
switch (status) {
|
||||
case DrawEntryStatus.pending:
|
||||
return 'info';
|
||||
case DrawEntryStatus.winner:
|
||||
return 'warning';
|
||||
case DrawEntryStatus.claimed:
|
||||
return 'success';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const StatusText: FCWithChildren<{ entry: DrawEntry }> = ({ entry }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
const [error, setError] = React.useState<string>();
|
||||
const [showWalletCapture, setShowWalletCapture] = React.useState(false);
|
||||
|
||||
const clear = () => {
|
||||
setShowWalletCapture(false);
|
||||
setError(undefined);
|
||||
setBusy(false);
|
||||
};
|
||||
|
||||
const handleStartWalletCapture = async () => {
|
||||
setBusy(true);
|
||||
setShowWalletCapture(true);
|
||||
};
|
||||
|
||||
const cancelEndWalletCapture = async () => {
|
||||
setBusy(false);
|
||||
setShowWalletCapture(false);
|
||||
};
|
||||
|
||||
const handleEndWalletCapture = async () => {
|
||||
setBusy(true);
|
||||
setShowWalletCapture(false);
|
||||
|
||||
if (!context.walletAddress) {
|
||||
setError('Wallet address is not set');
|
||||
return;
|
||||
}
|
||||
if (!entry.draw_id) {
|
||||
setError('Task id is not set');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await context.claim(entry.draw_id, context.walletAddress);
|
||||
} catch (e) {
|
||||
const message = `${e}`;
|
||||
console.error('Failed to submit claim');
|
||||
setError(message);
|
||||
}
|
||||
setBusy(false);
|
||||
};
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Alert severity="error" variant="filled">
|
||||
<AlertTitle>Oh no! Failed to submit claim</AlertTitle>
|
||||
{error}
|
||||
<Button variant="contained" color="secondary" size="small" onClick={() => clear()} sx={{ mx: 2 }}>
|
||||
Try again!
|
||||
</Button>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
if (showWalletCapture) {
|
||||
return (
|
||||
<Dialog open fullWidth onBackdropClick={cancelEndWalletCapture}>
|
||||
<TestAndEarnEnterWalletAddress onSubmit={handleEndWalletCapture} />
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
switch (entry.status) {
|
||||
case DrawEntryStatus.pending:
|
||||
return <>{Content.testAndEarn.status.text.Pending}</>;
|
||||
case DrawEntryStatus.winner:
|
||||
return (
|
||||
<>
|
||||
{Content.testAndEarn.status.text.Winner}
|
||||
<LoadingButton
|
||||
loading={busy}
|
||||
disabled={busy}
|
||||
variant="contained"
|
||||
sx={{ ml: 2 }}
|
||||
size="small"
|
||||
onClick={handleStartWalletCapture}
|
||||
>
|
||||
{Content.testAndEarn.winner.claimButton.text}
|
||||
</LoadingButton>
|
||||
</>
|
||||
);
|
||||
case DrawEntryStatus.claimed:
|
||||
return <>{Content.testAndEarn.status.text.Claimed}</>;
|
||||
case DrawEntryStatus.noWin:
|
||||
return <>{Content.testAndEarn.status.text.NoWin}</>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const TestAndEarnDraws: FCWithChildren<{
|
||||
sx?: SxProps;
|
||||
}> = () => {
|
||||
const context = useTestAndEarnContext();
|
||||
|
||||
const draws = React.useMemo<DrawEntry[]>(
|
||||
() =>
|
||||
(context.draws?.draws || []).map((item) => ({
|
||||
...item,
|
||||
timestamp: DateTime.fromISO(item.timestamp).toLocaleString(DateTime.DATETIME_FULL),
|
||||
})),
|
||||
[context.draws?.draws],
|
||||
);
|
||||
|
||||
if (!context.draws) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card sx={{ mb: 2 }}>
|
||||
<CardContent>
|
||||
<Typography mb={2}>Here is a history of the tasks you have completed:</Typography>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableBody>
|
||||
{draws.map((entry) => (
|
||||
<TableRow key={entry.draw_id}>
|
||||
<TableCell width="150px">{entry.timestamp}</TableCell>
|
||||
<TableCell width="150px">
|
||||
<Tooltip arrow title={`Task Id: ${entry.draw_id}`}>
|
||||
<Chip label={statusToText(entry.status)} color={statusToColor(entry.status)} />
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<StatusText entry={entry} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{entry.id} <CopyToClipboard iconButton light text={entry.id} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export const TestAndEarnDrawsWithState: FCWithChildren<{
|
||||
sx?: SxProps;
|
||||
}> = ({ sx }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
|
||||
const drawCount = context.draws?.draws?.length || 0;
|
||||
if (drawCount < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <TestAndEarnDraws sx={sx} />;
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { Box } from '@mui/material';
|
||||
import { TestAndEarnEnterWalletAddress } from './TestAndEarnEnterWalletAddress';
|
||||
import { TestAndEarnContextProvider } from './context/TestAndEarnContext';
|
||||
import { NymShipyardTheme } from '../../theme';
|
||||
|
||||
export default {
|
||||
title: 'Growth/TestAndEarn/Components/Enter wallet address',
|
||||
component: TestAndEarnEnterWalletAddress,
|
||||
} as ComponentMeta<typeof TestAndEarnEnterWalletAddress>;
|
||||
|
||||
export const Empty = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnContextProvider>
|
||||
<Box minWidth="25vw" maxWidth={500}>
|
||||
<TestAndEarnEnterWalletAddress sx={{ width: '100%' }} />
|
||||
</Box>
|
||||
</TestAndEarnContextProvider>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
|
||||
export const ErrorValue = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnContextProvider>
|
||||
<Box minWidth="25vw" maxWidth={500}>
|
||||
<TestAndEarnEnterWalletAddress initialValue="this is a bad value" sx={{ width: '100%' }} />
|
||||
</Box>
|
||||
</TestAndEarnContextProvider>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
|
||||
export const ValidValue = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnContextProvider>
|
||||
<Box minWidth="25vw" maxWidth={500}>
|
||||
<TestAndEarnEnterWalletAddress initialValue="n1xr4w0kddak8d8zlfmu8sl6dk2r4p9uhhzzlaec" sx={{ width: '100%' }} />
|
||||
</Box>
|
||||
</TestAndEarnContextProvider>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
@@ -1,37 +0,0 @@
|
||||
import React from 'react';
|
||||
import { WalletAddressFormField } from '@nymproject/react/account/WalletAddressFormField';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { Box, Button, Paper, Stack } from '@mui/material';
|
||||
import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
|
||||
export const TestAndEarnEnterWalletAddress: FCWithChildren<{
|
||||
initialValue?: string;
|
||||
placeholder?: string;
|
||||
onSubmit?: () => Promise<void> | void;
|
||||
sx?: SxProps;
|
||||
}> = ({ initialValue, placeholder, onSubmit }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
const [isAddressValid, setAddressIsValid] = React.useState(false);
|
||||
return (
|
||||
<Paper sx={{ py: 4, px: 2 }}>
|
||||
<Stack spacing={4}>
|
||||
<Box>
|
||||
<WalletAddressFormField
|
||||
label="Wallet address"
|
||||
initialValue={initialValue}
|
||||
placeholder={placeholder || 'Please enter your wallet address'}
|
||||
onChanged={context.setWalletAddress}
|
||||
onValidate={setAddressIsValid}
|
||||
sx={{ width: '80%' }}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Button variant="contained" endIcon={<ArrowCircleRightIcon />} disabled={!isAddressValid} onClick={onSubmit}>
|
||||
Submit
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box, Button } from '@mui/material';
|
||||
|
||||
export const TestAndEarnError: FCWithChildren<{ error?: string }> = ({ error = 'An error has occurred' }) => (
|
||||
<Box>
|
||||
<Box mb={4} fontWeight="bold">
|
||||
{error}
|
||||
</Box>
|
||||
<Button variant="outlined" color="secondary">
|
||||
Send us an error report
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
@@ -1,162 +0,0 @@
|
||||
/* eslint-disable react/jsx-pascal-case */
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { Alert, Box } from '@mui/material';
|
||||
import { NymShipyardTheme } from 'src/theme';
|
||||
import { TestAndEarnPopup, TestAndEarnPopupContent } from './TestAndEarnPopup';
|
||||
import { TestAndEarnContextProvider } from './context/TestAndEarnContext';
|
||||
import { MockProvider } from '../../context/mocks/main';
|
||||
import { ConnectionStatusKind } from '../../types';
|
||||
import {
|
||||
MockTestAndEarnProvider_NotRegistered,
|
||||
MockTestAndEarnProvider_RegisteredAndError,
|
||||
MockTestAndEarnProvider_RegisteredWithDraws,
|
||||
MockTestAndEarnProvider_RegisteredWithDrawsAndEntry,
|
||||
MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndNoWinner,
|
||||
MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner,
|
||||
MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerClaimed,
|
||||
MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerCollectWallet,
|
||||
MockTestAndEarnProvider_RegisteredWithDrawsNoCurrent,
|
||||
} from './context/mocks/TestAndEarnContext';
|
||||
|
||||
export default {
|
||||
title: 'Growth/TestAndEarn/Content/Popup',
|
||||
component: TestAndEarnPopupContent,
|
||||
} as ComponentMeta<typeof TestAndEarnPopupContent>;
|
||||
|
||||
const MacOSWindow: FCWithChildren<{
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
title?: string;
|
||||
children: React.ReactNode;
|
||||
}> = ({ title, width, height, children }) => (
|
||||
<Box sx={{ border: '1px solid #EEEEEE', width, height }}>
|
||||
<Box sx={{ background: '#EEEEEE', display: 'grid', gridTemplateColumns: 'auto auto', gridTemplateRows: 'auto' }}>
|
||||
<Box ml={1}>
|
||||
<svg width="52px" height="12px" viewBox="0 0 52 12" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Components" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
|
||||
<g id="macOS" transform="translate(-600.000000, -220.000000)">
|
||||
<g id="Group" transform="translate(600.000000, 220.000000)" strokeWidth="0.5">
|
||||
<g id="Traffic-Lights">
|
||||
<circle id="Traffic-Light---Zoom" stroke="#1BAC2C" fill="#2ACB42" cx="46" cy="6" r="5.75" />
|
||||
<circle id="Traffic-Light---Minimise" stroke="#DFA023" fill="#FFC12F" cx="26" cy="6" r="5.75" />
|
||||
<circle id="Traffic-Light---Close" stroke="#E24640" fill="#FF6157" cx="6" cy="6" r="5.75" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
alignSelf: 'center',
|
||||
color: '#000000',
|
||||
opacity: 0.848675272,
|
||||
fontSize: 13,
|
||||
}}
|
||||
>
|
||||
{title || 'Window title'}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ overflowY: 'scroll', height: 'calc(100% - 25px)' }}>{children}</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const Wrapper: FCWithChildren<{ text: React.ReactNode }> = ({ text }) => (
|
||||
<NymShipyardTheme>
|
||||
<Alert severity="info" sx={{ mb: 4 }}>
|
||||
{text}
|
||||
</Alert>
|
||||
<MacOSWindow width={700} height={600} title="Test&Earn">
|
||||
<TestAndEarnPopup />
|
||||
</MacOSWindow>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
|
||||
export const Stage0 = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_NotRegistered>
|
||||
<Wrapper text="The user sees this content when they have not joined Test&Earn." />
|
||||
</MockTestAndEarnProvider_NotRegistered>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage1EnterDraw = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDraws>
|
||||
<Wrapper text="The user has signed up and can see the next draw and choose the enter." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDraws>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage2GetTask = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntry>
|
||||
<Wrapper text="The user has entered a draw and can view the word of the day if they missed the popup notification." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntry>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage3Winner = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner>
|
||||
<Wrapper text="The user has won and can claim their prize." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage3NoPrize = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndNoWinner>
|
||||
<Wrapper text="The user has not won. A winner has been announced." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndNoWinner>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage4EnterWalletAddress = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerCollectWallet>
|
||||
<Wrapper text="The user is a winner, claims their prize and enters their wallet address." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerCollectWallet>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage5ClaimedPrize = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerClaimed>
|
||||
<Wrapper text="The user is a winner and has claimed their prize." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerClaimed>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage6DrawsFinished = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsNoCurrent>
|
||||
<Wrapper text="There are no more draws. The user can see their entries and prizes they have claimed." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsNoCurrent>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Connecting = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connecting}>
|
||||
<TestAndEarnContextProvider>
|
||||
<Wrapper text="Test&Earn requires the user to be connected to talk the API. This is shown while connecting." />
|
||||
</TestAndEarnContextProvider>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Disconnected = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.disconnected}>
|
||||
<TestAndEarnContextProvider>
|
||||
<Wrapper text="Test&Earn requires the user to be connected to talk the API. This is shown when not connected." />
|
||||
</TestAndEarnContextProvider>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Error = () => (
|
||||
<MockProvider>
|
||||
<MockTestAndEarnProvider_RegisteredAndError>
|
||||
<Wrapper text="The user see this with details about errors. They can submit an error report." />
|
||||
</MockTestAndEarnProvider_RegisteredAndError>
|
||||
</MockProvider>
|
||||
);
|
||||
@@ -1,118 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box, CircularProgress, LinearProgress, Stack, Typography } from '@mui/material';
|
||||
import { useClientContext } from '../../context/main';
|
||||
import ErrorContent from './content/TestAndEarn/Error.mdx';
|
||||
import ContentStep0 from './content/TestAndEarn/Stage0_intro.mdx';
|
||||
import ContentNotAvailable from './content/TestAndEarnNotAvaialble.mdx';
|
||||
import { ConnectionStatusKind } from '../../types';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
import { TestAndEarnWinnerWithState } from './TestAndEarnWinner';
|
||||
import { TestAndEarnCurrentDrawWithState } from './TestAndEarnCurrentDraw';
|
||||
import { TestAndEarnDrawsWithState } from './TestAndEarnDraws';
|
||||
|
||||
enum Stages {
|
||||
mustRegister = 'mustRegister',
|
||||
registered = 'registered',
|
||||
}
|
||||
|
||||
export const TestAndEarnPopupContent: FCWithChildren<{
|
||||
stage?: string;
|
||||
connectionStatus?: ConnectionStatusKind;
|
||||
error?: string;
|
||||
}> = ({ connectionStatus, error, stage = Stages.mustRegister }) => {
|
||||
if (error) {
|
||||
return (
|
||||
<Box p={4}>
|
||||
<ErrorContent error={error} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (!connectionStatus || connectionStatus === ConnectionStatusKind.disconnected) {
|
||||
return (
|
||||
<Box p={4}>
|
||||
<ContentNotAvailable />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (connectionStatus === ConnectionStatusKind.connecting || connectionStatus === ConnectionStatusKind.disconnecting) {
|
||||
return (
|
||||
<Box p={4} justifyContent="center" alignItems="center" display="flex">
|
||||
<CircularProgress />
|
||||
<Typography ml={3}>Please wait...</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
switch (stage) {
|
||||
case Stages.mustRegister:
|
||||
return (
|
||||
<Box p={4}>
|
||||
<ContentStep0 />
|
||||
</Box>
|
||||
);
|
||||
case Stages.registered:
|
||||
return (
|
||||
<Box p={4}>
|
||||
<TestAndEarnWinnerWithState />
|
||||
<TestAndEarnCurrentDrawWithState />
|
||||
<TestAndEarnDrawsWithState />
|
||||
</Box>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Box p={4}>
|
||||
<Stack direction="row" spacing={2} display="flex" alignItems="center">
|
||||
<CircularProgress />
|
||||
<Box>Waiting for task information...</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const TestAndEarnPopup: FCWithChildren = () => {
|
||||
const clientContext = useClientContext();
|
||||
const context = useTestAndEarnContext();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (clientContext.connectionStatus === ConnectionStatusKind.connected) {
|
||||
context.refresh();
|
||||
}
|
||||
}, [clientContext.connectionStatus]);
|
||||
|
||||
const stage = React.useMemo<Stages>(() => {
|
||||
if (context.registration) {
|
||||
return Stages.registered;
|
||||
}
|
||||
return Stages.mustRegister;
|
||||
}, [context.registration?.id]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const interval = setInterval(context.refresh, 1000 * 60 * 5);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
if (!context.loadedOnce && clientContext.connectionStatus === ConnectionStatusKind.connected) {
|
||||
const message = 'Waiting for data to be transferred over the mixnet...';
|
||||
return (
|
||||
<Box p={4}>
|
||||
<Stack direction="row" spacing={2} display="flex" alignItems="center">
|
||||
<CircularProgress />
|
||||
<Box>{message}</Box>
|
||||
{/* {process.env.NODE_ENV === 'development' && <pre>{JSON.stringify(context, null, 2)}</pre>} */}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{context.loading && <LinearProgress />}
|
||||
{/* <Button onClick={context.refresh}>Refresh</Button> */}
|
||||
<TestAndEarnPopupContent connectionStatus={clientContext.connectionStatus} stage={stage} error={context.error} />
|
||||
{/* {process.env.NODE_ENV === 'development' && <pre>{JSON.stringify(context, null, 2)}</pre>} */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user