Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e68a2c2ae3 | |||
| e945c94afc | |||
| 641b8179ba | |||
| df4385ab71 | |||
| 777166b93d | |||
| ccd889fc38 | |||
| d9291df347 | |||
| 5fcce2de48 | |||
| abf9ccb823 | |||
| 352527a098 | |||
| 724888e790 | |||
| 4b552db19f | |||
| 75aa2579a0 | |||
| 2493abcdff | |||
| 508f8324f9 | |||
| 468d0f38e9 | |||
| 3382642d70 | |||
| 8d3f1a3c38 | |||
| c5e695f8b5 | |||
| 80cfe83f9d | |||
| 76a22035be | |||
| 79bc2ab493 | |||
| 8c2c0f8033 | |||
| 5deb0875e2 | |||
| d1ee6faca8 | |||
| 756bad977f | |||
| b6448691ce | |||
| 47f7a5f795 | |||
| f29200431f | |||
| 0912627e1f | |||
| 70fdcc9be0 | |||
| 90da6f152b | |||
| ad547e516a | |||
| 61c0092f27 | |||
| 851b80aaab | |||
| 1397662fc9 | |||
| 1847c8fe73 | |||
| 8ffe3adf0d | |||
| 3e517b461c | |||
| e42e4ddb9b | |||
| a31f51b7ca | |||
| c41a600bcb | |||
| 7aa34391fb | |||
| f72b56d07e | |||
| 44ddbf9ac3 | |||
| 27c377860a | |||
| 9a3f60f224 | |||
| 5776600db3 | |||
| e851ad8b27 | |||
| 2e0e319511 | |||
| 6dd3e36acb | |||
| 40cbfccb73 | |||
| b49681ef1d | |||
| 7e9409bbef | |||
| a11e674967 | |||
| 2f4e505aac | |||
| 90d8e5305f | |||
| 81a678f9e2 | |||
| 0a4bbf2573 | |||
| 2ae5ebb8cb | |||
| 11be1f8e3e | |||
| 513baba3fd | |||
| 21b87f6af5 | |||
| fe59697aa9 | |||
| 566ceae325 | |||
| 6bc4d32573 | |||
| 1fc3c9f31c | |||
| 3cb08a410e | |||
| 1648aef1d3 | |||
| d98b6b107c | |||
| d3db0fc2cd |
@@ -19,10 +19,10 @@
|
||||
Cargo.* @durch @futurechimp @jstuczyn @neacsu @octol
|
||||
|
||||
# JS rules:
|
||||
*.js @mmsinclair @fmtabbara @Aid19801
|
||||
*.ts @mmsinclair @fmtabbara @Aid19801
|
||||
*.tsx @mmsinclair @fmtabbara @Aid19801
|
||||
*.jsx @mmsinclair @fmtabbara @Aid19801
|
||||
*.js @mmsinclair @fmtabbara
|
||||
*.ts @mmsinclair @fmtabbara
|
||||
*.tsx @mmsinclair @fmtabbara
|
||||
*.jsx @mmsinclair @fmtabbara
|
||||
|
||||
# Something looking like possible documentation rules:
|
||||
*.md @mfahampshire
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
[
|
||||
{
|
||||
"os":"ubuntu-latest",
|
||||
"rust":"stable",
|
||||
"runOnEvent":"workflow_dispatch"
|
||||
},
|
||||
|
||||
{
|
||||
"os":"windows-latest",
|
||||
"rust":"stable",
|
||||
"runOnEvent":"workflow_dispatch"
|
||||
},
|
||||
{
|
||||
"os":"macos-latest",
|
||||
"rust":"stable",
|
||||
"runOnEvent":"workflow_dispatch"
|
||||
},
|
||||
|
||||
{
|
||||
"os":"ubuntu-latest",
|
||||
"rust":"beta",
|
||||
"runOnEvent":"workflow_dispatch"
|
||||
},
|
||||
{
|
||||
"os":"windows-latest",
|
||||
"rust":"beta",
|
||||
"runOnEvent":"workflow_dispatch"
|
||||
},
|
||||
{
|
||||
"os":"macos-latest",
|
||||
"rust":"beta",
|
||||
"runOnEvent":"workflow_dispatch"
|
||||
},
|
||||
|
||||
{
|
||||
"os":"ubuntu-latest",
|
||||
"rust":"nightly",
|
||||
"runOnEvent":"workflow_dispatch"
|
||||
},
|
||||
{
|
||||
"os":"windows-latest",
|
||||
"rust":"nightly",
|
||||
"runOnEvent":"workflow_dispatch"
|
||||
},
|
||||
{
|
||||
"os":"macos-latest",
|
||||
"rust":"nightly",
|
||||
"runOnEvent":"workflow_dispatch"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,174 @@
|
||||
name: Nightly builds on dispatch
|
||||
|
||||
on: workflow_dispatch
|
||||
jobs:
|
||||
matrix_prep:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
# creates the matrix strategy from nightly_build_matrix_includes.json
|
||||
- uses: actions/checkout@v2
|
||||
- id: set-matrix
|
||||
uses: JoshuaTheMiller/conditional-build-matrix@main
|
||||
with:
|
||||
inputFile: '.github/workflows/nightly_build_matrix_on_dispatch.json'
|
||||
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
|
||||
build:
|
||||
needs: matrix_prep
|
||||
strategy:
|
||||
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace
|
||||
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace
|
||||
|
||||
- name: Reclaim some disk space (because Windows is being annoying)
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
- name: Run expensive tests
|
||||
if: github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master'
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --all-features -- --ignored
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- name: Reclaim some disk space (because Windows is being annoying)
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
name: Clippy checks
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --all-features
|
||||
|
||||
- name: Run clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace --all-targets -- -D warnings
|
||||
|
||||
- name: Reclaim some disk space
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-latest' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
# COCONUT stuff
|
||||
- name: Build all binaries with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Run all tests with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Reclaim some disk space (because Windows is being annoying)
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
- name: Run clippy with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace --all-targets --features=coconut -- -D warnings
|
||||
|
||||
# nym-wallet (the rust part)
|
||||
- name: Build nym-wallet rust code
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path nym-wallet/Cargo.toml --workspace
|
||||
|
||||
- name: Run nym-wallet tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path nym-wallet/Cargo.toml --workspace
|
||||
|
||||
- name: Check nym-wallet formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --manifest-path nym-wallet/Cargo.toml --all -- --check
|
||||
|
||||
- name: Run clippy for nym-wallet
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings
|
||||
|
||||
notification:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Collect jobs status
|
||||
uses: technote-space/workflow-conclusion-action@v2
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v2
|
||||
- name: Keybase - Node Install
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Keybase - Send Notification
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: nightly
|
||||
NYM_PROJECT_NAME: "Nym nightly build"
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
|
||||
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
|
||||
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
|
||||
KEYBASE_NYM_CHANNEL: "ci-nightly"
|
||||
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
@@ -9,7 +9,9 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
- socks5 client/websocket client: add `--force-register-gateway` flag, useful when rerunning init ([#1353])
|
||||
- nym-connect: initial proof-of-concept of a UI around the socks5 client was added
|
||||
- nym-connect: add ability to select network requester and gateway ([#1427]).
|
||||
- nym-connect: add ability to select network requester and gateway ([#1427])
|
||||
- nym-connect: add ability to export gateway keys as JSON
|
||||
- nym-connect: add auto updater
|
||||
- all: added network compilation target to `--help` (or `--version`) commands ([#1256]).
|
||||
- explorer-api: learned how to sum the delegations by owner in a new endpoint.
|
||||
- explorer-api: add apy values to `mix_nodes` endpoint
|
||||
@@ -22,9 +24,9 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- validator-api: Added new endpoints for coconut spending flow and communications with coconut & multisig contracts ([#1261])
|
||||
- validator-api: add `uptime`, `estimated_operator_apy`, `estimated_delegators_apy` to `/mixnodes/detailed` endpoint ([#1393])
|
||||
- network-statistics: a new mixnet service that aggregates and exposes anonymized data about mixnet services ([#1328])
|
||||
- wallet: when simulating gas costs, an automatic adjustment is being used ([#1388]).
|
||||
- mixnode: Added basic mixnode hardware reporting to the HTTP API ([#1308]).
|
||||
- validator-api: endpoint, in coconut mode, for returning the validator-api cosmos address ([#1404]).
|
||||
- validator-client: add `denom` argument and add simple test for querying an account balance
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -38,12 +40,15 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
### Changed
|
||||
|
||||
- nym-connect: reuse config id instead of creating a new id on each connection.
|
||||
- nym-connect: reuse config id instead of creating a new id on each connection
|
||||
- validator-client: created internal `Coin` type that replaces coins from `cosmrs` and `cosmwasm` for API entrypoints [[#1295]]
|
||||
- all: updated all `cosmwasm`-related dependencies to `1.0.0` and `cw-storage-plus` to `0.13.4` [[#1318]]
|
||||
- all: updated `rocket` to `0.5.0-rc.2`.
|
||||
- network-requester: allow to voluntarily store and send statistical data about the number of bytes the proxied server serves ([#1328])
|
||||
- gateway: allow to voluntarily send statistical data about the number of active inboxes served by a gateway ([#1376])
|
||||
- gateway & mixnode: move detailed build info back to `--version` from `--help`.
|
||||
- socks5 client/websocket client: upgrade to latest clap and switched to declarative commandline parsing.
|
||||
- validator-api: fee payment for multisig operations comes from the gateway account instead of the validator APIs' accounts ([#1419])
|
||||
|
||||
[#1249]: https://github.com/nymtech/nym/pull/1249
|
||||
[#1256]: https://github.com/nymtech/nym/pull/1256
|
||||
@@ -61,11 +66,18 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#1329]: https://github.com/nymtech/nym/pull/1329
|
||||
[#1353]: https://github.com/nymtech/nym/pull/1353
|
||||
[#1376]: https://github.com/nymtech/nym/pull/1376
|
||||
[#1388]: https://github.com/nymtech/nym/pull/1388
|
||||
[#1393]: https://github.com/nymtech/nym/pull/1393
|
||||
[#1404]: https://github.com/nymtech/nym/pull/1404
|
||||
[#1419]: https://github.com/nymtech/nym/pull/1419
|
||||
[#1427]: https://github.com/nymtech/nym/pull/1427
|
||||
|
||||
## [nym-wallet-v1.0.7](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.7) (2022-07-11)
|
||||
|
||||
- wallet: dark mode
|
||||
- wallet: when simulating gas costs, an automatic adjustment is being used ([#1388]).
|
||||
|
||||
[#1388]: https://github.com/nymtech/nym/pull/1388
|
||||
|
||||
## [nym-contracts-v1.0.1](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.1) (2022-06-22)
|
||||
|
||||
### Added
|
||||
|
||||
@@ -568,16 +568,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.1.8"
|
||||
version = "3.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c"
|
||||
checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"clap_lex",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"os_str_bytes",
|
||||
"once_cell",
|
||||
"strsim 0.10.0",
|
||||
"termcolor",
|
||||
"textwrap 0.15.0",
|
||||
@@ -585,9 +585,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.1.7"
|
||||
version = "3.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1"
|
||||
checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902"
|
||||
dependencies = [
|
||||
"heck 0.4.0",
|
||||
"proc-macro-error",
|
||||
@@ -596,6 +596,15 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "client-core"
|
||||
version = "1.0.1"
|
||||
@@ -773,9 +782,8 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||
|
||||
[[package]]
|
||||
name = "cosmos-sdk-proto"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f109fe191e73898d74b8020c50f86018364ad19bc30318aa074616c382b52856"
|
||||
version = "0.12.3"
|
||||
source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d"
|
||||
dependencies = [
|
||||
"prost 0.10.3",
|
||||
"prost-types 0.10.1",
|
||||
@@ -784,9 +792,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmrs"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8413275b23cb5a0734d9d1e3e33f0b5b94547c1e94776dbc3149dbf46588a533"
|
||||
version = "0.7.1"
|
||||
source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d"
|
||||
dependencies = [
|
||||
"bip32",
|
||||
"cosmos-sdk-proto",
|
||||
@@ -884,7 +891,7 @@ dependencies = [
|
||||
"async-trait",
|
||||
"bip39",
|
||||
"cfg-if 0.1.10",
|
||||
"clap 3.1.8",
|
||||
"clap 3.2.8",
|
||||
"coconut-interface",
|
||||
"credential-storage",
|
||||
"credentials",
|
||||
@@ -3049,7 +3056,7 @@ dependencies = [
|
||||
name = "nym-client"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"clap 2.34.0",
|
||||
"clap 3.2.8",
|
||||
"client-core",
|
||||
"coconut-interface",
|
||||
"config",
|
||||
@@ -3089,7 +3096,7 @@ dependencies = [
|
||||
"bandwidth-claim-contract",
|
||||
"bip39",
|
||||
"bs58",
|
||||
"clap 3.1.8",
|
||||
"clap 3.2.8",
|
||||
"coconut-interface",
|
||||
"colored",
|
||||
"config",
|
||||
@@ -3134,7 +3141,7 @@ version = "1.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58",
|
||||
"clap 3.1.8",
|
||||
"clap 3.2.8",
|
||||
"colored",
|
||||
"config",
|
||||
"crypto",
|
||||
@@ -3216,7 +3223,7 @@ dependencies = [
|
||||
name = "nym-socks5-client"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"clap 2.34.0",
|
||||
"clap 3.2.8",
|
||||
"client-core",
|
||||
"coconut-interface",
|
||||
"config",
|
||||
@@ -3500,9 +3507,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.10.0"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
|
||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
@@ -3567,9 +3574,6 @@ name = "os_str_bytes"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pairing"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
name = "nym-client"
|
||||
version = "1.0.1"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
|
||||
@@ -19,7 +20,7 @@ futures = "0.3" # bunch of futures stuff, however, now that I think about it, it
|
||||
# and the single instance of abortable we have should really be refactored anyway
|
||||
url = "2.2"
|
||||
|
||||
clap = "2.33.0" # for the command line arguments
|
||||
clap = { version = "3.2.8", features = ["cargo", "derive"] }
|
||||
dirs = "4.0"
|
||||
dotenv = "0.15.0" # for obtaining environmental variables (only used for RUST_LOG for time being)
|
||||
log = "0.4" # self explanatory
|
||||
|
||||
@@ -1,88 +1,100 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use clap::Args;
|
||||
use client_core::config::GatewayEndpoint;
|
||||
use config::NymConfig;
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::commands::override_config;
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use crate::commands::{
|
||||
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ENABLED_CREDENTIALS_MODE_ARG_NAME,
|
||||
ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
|
||||
use crate::{
|
||||
client::config::Config,
|
||||
commands::{override_config, OverrideConfig},
|
||||
};
|
||||
|
||||
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
let app = App::new("init")
|
||||
.about("Initialise a Nym client. Do this first!")
|
||||
.arg(Arg::with_name("id")
|
||||
.long("id")
|
||||
.help("Id of the nym-mixnet-client we want to create config for.")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
)
|
||||
.arg(Arg::with_name("gateway")
|
||||
.long("gateway")
|
||||
.help("Id of the gateway we are going to connect to.")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name("force-register-gateway")
|
||||
.long("force-register-gateway")
|
||||
.help("Force register gateway. WARNING: this will overwrite any existing keys for the given id, potentially causing loss of access.")
|
||||
.takes_value(false)
|
||||
)
|
||||
.arg(Arg::with_name("validators")
|
||||
.long("validators")
|
||||
.help("Comma separated list of rest endpoints of the validators")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(Arg::with_name("disable-socket")
|
||||
.long("disable-socket")
|
||||
.help("Whether to not start the websocket")
|
||||
)
|
||||
.arg(Arg::with_name("port")
|
||||
.short("p")
|
||||
.long("port")
|
||||
.help("Port for the socket (if applicable) to listen on in all subsequent runs")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name("fastmode")
|
||||
.long("fastmode")
|
||||
.hidden(true) // this will prevent this flag from being displayed in `--help`
|
||||
.help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init")
|
||||
);
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let app = app
|
||||
.arg(
|
||||
Arg::with_name(ENABLED_CREDENTIALS_MODE_ARG_NAME)
|
||||
.long(ENABLED_CREDENTIALS_MODE_ARG_NAME)
|
||||
.help("Set this client to work in a disabled credentials mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
|
||||
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
|
||||
)
|
||||
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
|
||||
.long(ETH_ENDPOINT_ARG_NAME)
|
||||
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true)
|
||||
.default_value_if(ENABLED_CREDENTIALS_MODE_ARG_NAME, None, DEFAULT_ETH_ENDPOINT)
|
||||
.required(true))
|
||||
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.long(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true)
|
||||
.default_value_if(ENABLED_CREDENTIALS_MODE_ARG_NAME, None, DEFAULT_ETH_PRIVATE_KEY)
|
||||
.required(true)
|
||||
);
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
use crate::commands::{DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY};
|
||||
|
||||
app
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct Init {
|
||||
/// Id of the nym-mixnet-client we want to create config for.
|
||||
#[clap(long)]
|
||||
id: String,
|
||||
|
||||
/// Id of the gateway we are going to connect to.
|
||||
#[clap(long)]
|
||||
gateway: Option<String>,
|
||||
|
||||
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
|
||||
/// potentially causing loss of access.
|
||||
#[clap(long)]
|
||||
force_register_gateway: bool,
|
||||
|
||||
/// Comma separated list of rest endpoints of the validators
|
||||
#[clap(long)]
|
||||
validators: Option<String>,
|
||||
|
||||
/// Whether to not start the websocket
|
||||
#[clap(long)]
|
||||
disable_socket: bool,
|
||||
|
||||
/// Port for the socket (if applicable) to listen on in all subsequent runs
|
||||
#[clap(short, long)]
|
||||
port: Option<u16>,
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[clap(long, hidden = true)]
|
||||
fastmode: bool,
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement. If this value is set, --eth-endpoint and
|
||||
/// --eth-private_key don't need to be set.
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(long, conflicts_with_all = &["eth-endpoint", "eth-private-key"])]
|
||||
enabled_credentials_mode: bool,
|
||||
|
||||
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
|
||||
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(
|
||||
long,
|
||||
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_ENDPOINT))
|
||||
)]
|
||||
eth_endpoint: String,
|
||||
|
||||
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
|
||||
/// want to set this value, use --enabled-credentials-mode instead")
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(
|
||||
long,
|
||||
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_PRIVATE_KEY))
|
||||
)]
|
||||
eth_private_key: String,
|
||||
}
|
||||
|
||||
pub async fn execute(matches: ArgMatches<'static>) {
|
||||
impl From<Init> for OverrideConfig {
|
||||
fn from(init_config: Init) -> Self {
|
||||
OverrideConfig {
|
||||
validators: init_config.validators,
|
||||
disable_socket: init_config.disable_socket,
|
||||
port: init_config.port,
|
||||
fastmode: init_config.fastmode,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
enabled_credentials_mode: init_config.enabled_credentials_mode,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
eth_private_key: Some(init_config.eth_private_key),
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
eth_endpoint: Some(init_config.eth_endpoint),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Init) {
|
||||
println!("Initialising client...");
|
||||
|
||||
let id = matches.value_of("id").unwrap(); // required for now
|
||||
let id = &args.id;
|
||||
|
||||
let already_init = Config::default_config_file_path(Some(id)).exists();
|
||||
if already_init {
|
||||
@@ -95,7 +107,7 @@ pub async fn execute(matches: ArgMatches<'static>) {
|
||||
|
||||
// Usually you only register with the gateway on the first init, however you can force
|
||||
// re-registering if wanted.
|
||||
let user_wants_force_register = matches.is_present("force-register-gateway");
|
||||
let user_wants_force_register = args.force_register_gateway;
|
||||
|
||||
// If the client was already initialized, don't generate new keys and don't re-register with
|
||||
// the gateway (because this would create a new shared key).
|
||||
@@ -103,14 +115,11 @@ pub async fn execute(matches: ArgMatches<'static>) {
|
||||
let register_gateway = !already_init || user_wants_force_register;
|
||||
|
||||
// Attempt to use a user-provided gateway, if possible
|
||||
let user_chosen_gateway_id = matches.value_of("gateway");
|
||||
let user_chosen_gateway_id = args.gateway.as_deref();
|
||||
|
||||
let mut config = Config::new(id);
|
||||
|
||||
config = override_config(config, &matches);
|
||||
if matches.is_present("fastmode") {
|
||||
config.get_base_mut().set_high_default_traffic_volume();
|
||||
}
|
||||
let override_config_fields = OverrideConfig::from(args.clone());
|
||||
config = override_config(config, override_config_fields);
|
||||
|
||||
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config).await;
|
||||
config.get_base_mut().with_gateway_endpoint(gateway);
|
||||
|
||||
@@ -2,14 +2,10 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::{Config, SocketType};
|
||||
use clap::ArgMatches;
|
||||
use clap::{Parser, Subcommand};
|
||||
use network_defaults::DEFAULT_NETWORK;
|
||||
use url::Url;
|
||||
|
||||
pub(crate) const ENABLED_CREDENTIALS_MODE_ARG_NAME: &str = "enabled-credentials-mode";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub(crate) const ETH_ENDPOINT_ARG_NAME: &str = "eth_endpoint";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub(crate) const ETH_PRIVATE_KEY_ARG_NAME: &str = "eth_private_key";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub(crate) const DEFAULT_ETH_ENDPOINT: &str =
|
||||
"https://rinkeby.infura.io/v3/00000000000000000000000000000000";
|
||||
@@ -21,6 +17,86 @@ pub(crate) mod init;
|
||||
pub(crate) mod run;
|
||||
pub(crate) mod upgrade;
|
||||
|
||||
fn long_version() -> String {
|
||||
format!(
|
||||
r#"
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
"#,
|
||||
"Build Timestamp:",
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
"Build Version:",
|
||||
env!("VERGEN_BUILD_SEMVER"),
|
||||
"Commit SHA:",
|
||||
env!("VERGEN_GIT_SHA"),
|
||||
"Commit Date:",
|
||||
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
|
||||
"Commit Branch:",
|
||||
env!("VERGEN_GIT_BRANCH"),
|
||||
"rustc Version:",
|
||||
env!("VERGEN_RUSTC_SEMVER"),
|
||||
"rustc Channel:",
|
||||
env!("VERGEN_RUSTC_CHANNEL"),
|
||||
"cargo Profile:",
|
||||
env!("VERGEN_CARGO_PROFILE"),
|
||||
"Network:",
|
||||
DEFAULT_NETWORK
|
||||
)
|
||||
}
|
||||
|
||||
fn long_version_static() -> &'static str {
|
||||
Box::leak(long_version().into_boxed_str())
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
|
||||
pub(crate) struct Cli {
|
||||
#[clap(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum Commands {
|
||||
/// Initialise a Nym client. Do this first!
|
||||
Init(init::Init),
|
||||
/// Run the Nym client with provided configuration client optionally overriding set parameters
|
||||
Run(run::Run),
|
||||
/// Try to upgrade the client
|
||||
Upgrade(upgrade::Upgrade),
|
||||
}
|
||||
|
||||
// Configuration that can be overridden.
|
||||
pub(crate) struct OverrideConfig {
|
||||
validators: Option<String>,
|
||||
disable_socket: bool,
|
||||
port: Option<u16>,
|
||||
fastmode: bool,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
enabled_credentials_mode: bool,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
eth_private_key: Option<String>,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
eth_endpoint: Option<String>,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Cli) {
|
||||
match &args.command {
|
||||
Commands::Init(m) => init::execute(m).await,
|
||||
Commands::Run(m) => run::execute(m).await,
|
||||
Commands::Upgrade(m) => upgrade::execute(m),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_validators(raw: &str) -> Vec<Url> {
|
||||
raw.split(',')
|
||||
.map(|raw_validator| {
|
||||
@@ -32,45 +108,58 @@ fn parse_validators(raw: &str) -> Vec<Url> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config {
|
||||
if let Some(raw_validators) = matches.value_of("validators") {
|
||||
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
|
||||
if let Some(raw_validators) = args.validators {
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_validator_apis(parse_validators(raw_validators));
|
||||
.set_custom_validator_apis(parse_validators(&raw_validators));
|
||||
}
|
||||
|
||||
if matches.is_present("disable-socket") {
|
||||
if args.disable_socket {
|
||||
config = config.with_socket(SocketType::None);
|
||||
}
|
||||
|
||||
if let Some(port) = matches.value_of("port").map(str::parse) {
|
||||
if let Err(err) = port {
|
||||
// if port was overridden, it must be parsable
|
||||
panic!("Invalid port value provided - {:?}", err);
|
||||
if let Some(port) = args.port {
|
||||
config = config.with_port(port);
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "eth"), not(feature = "coconut")))]
|
||||
{
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT.to_string());
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY.to_string());
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
{
|
||||
if args.enabled_credentials_mode {
|
||||
config.get_base_mut().with_disabled_credentials(false)
|
||||
}
|
||||
if let Some(eth_endpoint) = args.eth_endpoint {
|
||||
config.get_base_mut().with_eth_endpoint(eth_endpoint);
|
||||
}
|
||||
if let Some(eth_private_key) = args.eth_private_key {
|
||||
config.get_base_mut().with_eth_private_key(eth_private_key);
|
||||
}
|
||||
config = config.with_port(port.unwrap());
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
if let Some(eth_endpoint) = matches.value_of(ETH_ENDPOINT_ARG_NAME) {
|
||||
config.get_base_mut().with_eth_endpoint(eth_endpoint);
|
||||
} else if !cfg!(feature = "eth") {
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT);
|
||||
}
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
if let Some(eth_private_key) = matches.value_of(ETH_PRIVATE_KEY_ARG_NAME) {
|
||||
config.get_base_mut().with_eth_private_key(eth_private_key);
|
||||
} else if !cfg!(feature = "eth") {
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
|
||||
}
|
||||
|
||||
if matches.is_present(ENABLED_CREDENTIALS_MODE_ARG_NAME) {
|
||||
config.get_base_mut().with_disabled_credentials(false)
|
||||
if args.fastmode {
|
||||
config.get_base_mut().set_high_default_traffic_volume();
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use clap::CommandFactory;
|
||||
|
||||
#[test]
|
||||
fn verify_cli() {
|
||||
Cli::command().debug_assert();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +1,77 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::client::NymClient;
|
||||
use crate::commands::override_config;
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use crate::commands::{
|
||||
ENABLED_CREDENTIALS_MODE_ARG_NAME, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
|
||||
use crate::{
|
||||
client::{config::Config, NymClient},
|
||||
commands::{override_config, OverrideConfig},
|
||||
};
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
|
||||
use clap::Args;
|
||||
use config::NymConfig;
|
||||
use log::*;
|
||||
use version_checker::is_minor_version_compatible;
|
||||
|
||||
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
let app = App::new("run")
|
||||
.about("Run the Nym client with provided configuration client optionally overriding set parameters")
|
||||
.arg(Arg::with_name("id")
|
||||
.long("id")
|
||||
.help("Id of the nym-mixnet-client we want to run.")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
)
|
||||
// the rest of arguments are optional, they are used to override settings in config file
|
||||
.arg(Arg::with_name("validators")
|
||||
.long("validators")
|
||||
.help("Comma separated list rest rest endpoints of the validators")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(Arg::with_name("gateway")
|
||||
.long("gateway")
|
||||
.help("Id of the gateway we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name("disable-socket")
|
||||
.long("disable-socket")
|
||||
.help("Whether to not start the websocket")
|
||||
)
|
||||
.arg(Arg::with_name("port")
|
||||
.short("p")
|
||||
.long("port")
|
||||
.help("Port for the socket (if applicable) to listen on")
|
||||
.takes_value(true)
|
||||
);
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let app = app
|
||||
.arg(
|
||||
Arg::with_name(ENABLED_CREDENTIALS_MODE_ARG_NAME)
|
||||
.long(ENABLED_CREDENTIALS_MODE_ARG_NAME)
|
||||
.help("Set this client to work in a enabled credentials mode that would attempt to use gateway with bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
|
||||
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
|
||||
)
|
||||
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
|
||||
.long(ETH_ENDPOINT_ARG_NAME)
|
||||
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.long(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true));
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct Run {
|
||||
/// Id of the nym-mixnet-client we want to run.
|
||||
#[clap(long)]
|
||||
id: String,
|
||||
|
||||
app
|
||||
/// Comma separated list of rest endpoints of the validators
|
||||
#[clap(long)]
|
||||
validators: Option<String>,
|
||||
|
||||
/// Id of the gateway we want to connect to. If overridden, it is user's responsibility to
|
||||
/// ensure prior registration happened
|
||||
#[clap(long)]
|
||||
gateway: Option<String>,
|
||||
|
||||
/// Whether to not start the websocket
|
||||
#[clap(long)]
|
||||
disable_socket: bool,
|
||||
|
||||
/// Port for the socket to listen on
|
||||
#[clap(short, long)]
|
||||
port: Option<u16>,
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement. If this value is set, --eth-endpoint and
|
||||
/// --eth-private-key don't need to be set.
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(long, conflicts_with_all = &["eth-endpoint", "eth-private-key"])]
|
||||
enabled_credentials_mode: bool,
|
||||
|
||||
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
|
||||
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(long)]
|
||||
eth_endpoint: Option<String>,
|
||||
|
||||
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
|
||||
/// want to set this value, use --enabled-credentials-mode instead")
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(long)]
|
||||
eth_private_key: Option<String>,
|
||||
}
|
||||
|
||||
impl From<Run> for OverrideConfig {
|
||||
fn from(run_config: Run) -> Self {
|
||||
OverrideConfig {
|
||||
validators: run_config.validators,
|
||||
disable_socket: run_config.disable_socket,
|
||||
port: run_config.port,
|
||||
fastmode: false,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
enabled_credentials_mode: run_config.enabled_credentials_mode,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
eth_private_key: run_config.eth_private_key,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
eth_endpoint: run_config.eth_endpoint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this only checks compatibility between config the binary. It does not take into consideration
|
||||
@@ -84,8 +93,8 @@ fn version_check(cfg: &Config) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn execute(matches: ArgMatches<'static>) {
|
||||
let id = matches.value_of("id").unwrap();
|
||||
pub(crate) async fn execute(args: &Run) {
|
||||
let id = &args.id;
|
||||
|
||||
let mut config = match Config::load_from_file(Some(id)) {
|
||||
Ok(cfg) => cfg,
|
||||
@@ -95,7 +104,8 @@ pub async fn execute(matches: ArgMatches<'static>) {
|
||||
}
|
||||
};
|
||||
|
||||
config = override_config(config, &matches);
|
||||
let override_config_fields = OverrideConfig::from(args.clone());
|
||||
config = override_config(config, override_config_fields);
|
||||
|
||||
if !version_check(&config) {
|
||||
error!("failed the local version check");
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::{Config, MISSING_VALUE};
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::defaults::default_api_endpoints;
|
||||
use config::NymConfig;
|
||||
|
||||
use config::{defaults::default_api_endpoints, NymConfig};
|
||||
use version_checker::Version;
|
||||
|
||||
use clap::Args;
|
||||
use std::fmt::Display;
|
||||
use std::process;
|
||||
use version_checker::Version;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn fail_upgrade<D1: Display, D2: Display>(from_version: D1, to_version: D2) -> ! {
|
||||
@@ -49,14 +50,11 @@ fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> !
|
||||
process::exit(1)
|
||||
}
|
||||
|
||||
pub fn command_args<'a, 'b>() -> App<'a, 'b> {
|
||||
App::new("upgrade").about("Try to upgrade the client").arg(
|
||||
Arg::with_name("id")
|
||||
.long("id")
|
||||
.help("Id of the nym-client we want to upgrade")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct Upgrade {
|
||||
/// Id of the nym-client we want to upgrade
|
||||
#[clap(long)]
|
||||
id: String,
|
||||
}
|
||||
|
||||
fn parse_config_version(config: &Config) -> Version {
|
||||
@@ -95,7 +93,7 @@ fn parse_package_version() -> Version {
|
||||
|
||||
fn minor_0_12_upgrade(
|
||||
mut config: Config,
|
||||
_matches: &ArgMatches<'_>,
|
||||
_matches: &Upgrade,
|
||||
config_version: &Version,
|
||||
package_version: &Version,
|
||||
) -> Config {
|
||||
@@ -131,7 +129,7 @@ fn minor_0_12_upgrade(
|
||||
config
|
||||
}
|
||||
|
||||
fn do_upgrade(mut config: Config, matches: &ArgMatches<'_>, package_version: &Version) {
|
||||
fn do_upgrade(mut config: Config, args: &Upgrade, package_version: &Version) {
|
||||
loop {
|
||||
let config_version = parse_config_version(&config);
|
||||
|
||||
@@ -143,7 +141,7 @@ fn do_upgrade(mut config: Config, matches: &ArgMatches<'_>, package_version: &Ve
|
||||
config = match config_version.major {
|
||||
0 => match config_version.minor {
|
||||
9 | 10 => outdated_upgrade(&config_version, package_version),
|
||||
11 => minor_0_12_upgrade(config, matches, &config_version, package_version),
|
||||
11 => minor_0_12_upgrade(config, args, &config_version, package_version),
|
||||
_ => unsupported_upgrade(&config_version, package_version),
|
||||
},
|
||||
_ => unsupported_upgrade(&config_version, package_version),
|
||||
@@ -151,10 +149,10 @@ fn do_upgrade(mut config: Config, matches: &ArgMatches<'_>, package_version: &Ve
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute(matches: &ArgMatches<'_>) {
|
||||
pub(crate) fn execute(args: &Upgrade) {
|
||||
let package_version = parse_package_version();
|
||||
|
||||
let id = matches.value_of("id").unwrap();
|
||||
let id = &args.id;
|
||||
|
||||
let existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| {
|
||||
eprintln!("failed to load existing config file! - {:?}", err);
|
||||
@@ -167,5 +165,5 @@ pub fn execute(matches: &ArgMatches<'_>) {
|
||||
}
|
||||
|
||||
// here be upgrade path to 0.9.X and beyond based on version number from config
|
||||
do_upgrade(existing_config, matches, &package_version)
|
||||
do_upgrade(existing_config, args, &package_version)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{crate_version, App, ArgMatches};
|
||||
use network_defaults::DEFAULT_NETWORK;
|
||||
use clap::{crate_version, Parser};
|
||||
|
||||
pub mod client;
|
||||
pub mod commands;
|
||||
@@ -14,30 +13,8 @@ async fn main() {
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
|
||||
let arg_matches = App::new("Nym Client")
|
||||
.version(crate_version!())
|
||||
.long_version(&*long_version())
|
||||
.author("Nymtech")
|
||||
.about("Implementation of the Nym Client")
|
||||
.subcommand(commands::init::command_args())
|
||||
.subcommand(commands::run::command_args())
|
||||
.subcommand(commands::upgrade::command_args())
|
||||
.get_matches();
|
||||
|
||||
execute(arg_matches).await;
|
||||
}
|
||||
|
||||
async fn execute(matches: ArgMatches<'static>) {
|
||||
match matches.subcommand() {
|
||||
("init", Some(m)) => commands::init::execute(m.clone()).await,
|
||||
("run", Some(m)) => commands::run::execute(m.clone()).await,
|
||||
("upgrade", Some(m)) => commands::upgrade::execute(m),
|
||||
_ => println!("{}", usage()),
|
||||
}
|
||||
}
|
||||
|
||||
fn usage() -> &'static str {
|
||||
"usage: --help to see available options.\n\n"
|
||||
let args = commands::Cli::parse();
|
||||
commands::execute(&args).await;
|
||||
}
|
||||
|
||||
fn banner() -> String {
|
||||
@@ -57,40 +34,6 @@ fn banner() -> String {
|
||||
)
|
||||
}
|
||||
|
||||
fn long_version() -> String {
|
||||
format!(
|
||||
r#"
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
"#,
|
||||
"Build Timestamp:",
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
"Build Version:",
|
||||
env!("VERGEN_BUILD_SEMVER"),
|
||||
"Commit SHA:",
|
||||
env!("VERGEN_GIT_SHA"),
|
||||
"Commit Date:",
|
||||
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
|
||||
"Commit Branch:",
|
||||
env!("VERGEN_GIT_BRANCH"),
|
||||
"rustc Version:",
|
||||
env!("VERGEN_RUSTC_SEMVER"),
|
||||
"rustc Channel:",
|
||||
env!("VERGEN_RUSTC_CHANNEL"),
|
||||
"cargo Profile:",
|
||||
env!("VERGEN_CARGO_PROFILE"),
|
||||
"Network:",
|
||||
DEFAULT_NETWORK
|
||||
)
|
||||
}
|
||||
|
||||
fn setup_logging() {
|
||||
let mut log_builder = pretty_env_logger::formatted_timed_builder();
|
||||
if let Ok(s) = ::std::env::var("RUST_LOG") {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
name = "nym-socks5-client"
|
||||
version = "1.0.1"
|
||||
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"
|
||||
rust-version = "1.56"
|
||||
|
||||
@@ -10,7 +11,7 @@ name = "nym_socks5"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
clap = { version = "3.2.8", features = ["cargo", "derive"] }
|
||||
dirs = "4.0"
|
||||
dotenv = "0.15.0"
|
||||
futures = "0.3"
|
||||
|
||||
@@ -287,12 +287,15 @@ impl NymClient {
|
||||
pub async fn run_and_listen(&mut self, mut receiver: Socks5ControlMessageReceiver) {
|
||||
self.start().await;
|
||||
tokio::select! {
|
||||
message = receiver.next() => match message {
|
||||
Some(Socks5ControlMessage::Stop) => {
|
||||
log::info!("Received: {:?}", message);
|
||||
log::info!("Shutting down");
|
||||
message = receiver.next() => {
|
||||
log::debug!("Received message: {:?}", message);
|
||||
match message {
|
||||
Some(Socks5ControlMessage::Stop) => {
|
||||
log::info!("Shutting down");
|
||||
log::info!("Graceful shutdown of tasks not yet implemented, you might see (harmless) panics until then");
|
||||
}
|
||||
None => log::debug!("None"),
|
||||
}
|
||||
None => log::info!("none"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,91 +1,100 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use clap::Args;
|
||||
use client_core::config::GatewayEndpoint;
|
||||
use config::NymConfig;
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::commands::override_config;
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use crate::commands::{
|
||||
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ENABLED_CREDENTIALS_MODE_ARG_NAME,
|
||||
ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
|
||||
use crate::{
|
||||
client::config::Config,
|
||||
commands::{override_config, OverrideConfig},
|
||||
};
|
||||
|
||||
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
let app = App::new("init")
|
||||
.about("Initialise a Nym client. Do this first!")
|
||||
.arg(Arg::with_name("id")
|
||||
.long("id")
|
||||
.help("Id of the nym-mixnet-client we want to create config for.")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
)
|
||||
.arg(Arg::with_name("provider")
|
||||
.long("provider")
|
||||
.help("Address of the socks5 provider to send messages to.")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
)
|
||||
.arg(Arg::with_name("gateway")
|
||||
.long("gateway")
|
||||
.help("Id of the gateway we are going to connect to.")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name("force-register-gateway")
|
||||
.long("force-register-gateway")
|
||||
.help("Force register gateway. WARNING: this will overwrite any existing keys for the given id, potentially causing loss of access.")
|
||||
.takes_value(false)
|
||||
)
|
||||
.arg(Arg::with_name("validators")
|
||||
.long("validators")
|
||||
.help("Comma separated list of rest endpoints of the validators")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(Arg::with_name("port")
|
||||
.short("p")
|
||||
.long("port")
|
||||
.help("Port for the socket to listen on in all subsequent runs")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name("fastmode")
|
||||
.long("fastmode")
|
||||
.hidden(true) // this will prevent this flag from being displayed in `--help`
|
||||
.help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init")
|
||||
);
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let app = app
|
||||
.arg(
|
||||
Arg::with_name(ENABLED_CREDENTIALS_MODE_ARG_NAME)
|
||||
.long(ENABLED_CREDENTIALS_MODE_ARG_NAME)
|
||||
.help("Set this client to work in a enabled credentials mode that would attempt to use gateway with bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
|
||||
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
|
||||
)
|
||||
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
|
||||
.long(ETH_ENDPOINT_ARG_NAME)
|
||||
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true)
|
||||
.default_value_if(ENABLED_CREDENTIALS_MODE_ARG_NAME, None, DEFAULT_ETH_ENDPOINT)
|
||||
.required(true))
|
||||
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.long(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true)
|
||||
.default_value_if(ENABLED_CREDENTIALS_MODE_ARG_NAME, None, DEFAULT_ETH_PRIVATE_KEY)
|
||||
.required(true)
|
||||
);
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
use crate::commands::{DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY};
|
||||
|
||||
app
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct Init {
|
||||
/// Id of the nym-mixnet-client we want to create config for.
|
||||
#[clap(long)]
|
||||
id: String,
|
||||
|
||||
/// Address of the socks5 provider to send messages to.
|
||||
#[clap(long)]
|
||||
provider: String,
|
||||
|
||||
/// Id of the gateway we are going to connect to.
|
||||
#[clap(long)]
|
||||
gateway: Option<String>,
|
||||
|
||||
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
|
||||
/// potentially causing loss of access.
|
||||
#[clap(long)]
|
||||
force_register_gateway: bool,
|
||||
|
||||
/// Comma separated list of rest endpoints of the validators
|
||||
#[clap(long)]
|
||||
validators: Option<String>,
|
||||
|
||||
/// Port for the socket to listen on in all subsequent runs
|
||||
#[clap(short, long)]
|
||||
port: Option<u16>,
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[clap(long, hidden = true)]
|
||||
fastmode: bool,
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement. If this value is set, --eth-endpoint and
|
||||
/// --eth-private_key don't need to be set.
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(long, conflicts_with_all = &["eth-endpoint", "eth-private-key"])]
|
||||
enabled_credentials_mode: bool,
|
||||
|
||||
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
|
||||
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(
|
||||
long,
|
||||
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_ENDPOINT))
|
||||
)]
|
||||
eth_endpoint: String,
|
||||
|
||||
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
|
||||
/// want to set this value, use --enabled-credentials-mode instead")
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(
|
||||
long,
|
||||
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_PRIVATE_KEY))
|
||||
)]
|
||||
eth_private_key: String,
|
||||
}
|
||||
|
||||
pub async fn execute(matches: ArgMatches<'static>) {
|
||||
impl From<Init> for OverrideConfig {
|
||||
fn from(init_config: Init) -> Self {
|
||||
OverrideConfig {
|
||||
validators: init_config.validators,
|
||||
port: init_config.port,
|
||||
fastmode: init_config.fastmode,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
enabled_credentials_mode: init_config.enabled_credentials_mode,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
eth_private_key: Some(init_config.eth_private_key),
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
eth_endpoint: Some(init_config.eth_endpoint),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Init) {
|
||||
println!("Initialising client...");
|
||||
|
||||
let id = matches.value_of("id").unwrap(); // required for now
|
||||
let provider_address = matches.value_of("provider").unwrap();
|
||||
let id = &args.id;
|
||||
let provider_address = &args.provider;
|
||||
|
||||
let already_init = Config::default_config_file_path(Some(id)).exists();
|
||||
if already_init {
|
||||
@@ -98,7 +107,7 @@ pub async fn execute(matches: ArgMatches<'static>) {
|
||||
|
||||
// Usually you only register with the gateway on the first init, however you can force
|
||||
// re-registering if wanted.
|
||||
let user_wants_force_register = matches.is_present("force-register-gateway");
|
||||
let user_wants_force_register = args.force_register_gateway;
|
||||
|
||||
// If the client was already initialized, don't generate new keys and don't re-register with
|
||||
// the gateway (because this would create a new shared key).
|
||||
@@ -106,14 +115,11 @@ pub async fn execute(matches: ArgMatches<'static>) {
|
||||
let register_gateway = !already_init || user_wants_force_register;
|
||||
|
||||
// Attempt to use a user-provided gateway, if possible
|
||||
let user_chosen_gateway_id = matches.value_of("gateway");
|
||||
let user_chosen_gateway_id = args.gateway.as_deref();
|
||||
|
||||
let mut config = Config::new(id, provider_address);
|
||||
|
||||
config = override_config(config, &matches);
|
||||
if matches.is_present("fastmode") {
|
||||
config.get_base_mut().set_high_default_traffic_volume();
|
||||
}
|
||||
let override_config_fields = OverrideConfig::from(args.clone());
|
||||
config = override_config(config, override_config_fields);
|
||||
|
||||
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config).await;
|
||||
config.get_base_mut().with_gateway_endpoint(gateway);
|
||||
|
||||
@@ -2,18 +2,14 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::Config;
|
||||
use clap::ArgMatches;
|
||||
use clap::{Parser, Subcommand};
|
||||
use network_defaults::DEFAULT_NETWORK;
|
||||
use url::Url;
|
||||
|
||||
pub mod init;
|
||||
pub(crate) mod run;
|
||||
pub(crate) mod upgrade;
|
||||
|
||||
pub(crate) const ENABLED_CREDENTIALS_MODE_ARG_NAME: &str = "enabled-credentials-mode";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub(crate) const ETH_ENDPOINT_ARG_NAME: &str = "eth_endpoint";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub(crate) const ETH_PRIVATE_KEY_ARG_NAME: &str = "eth_private_key";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub(crate) const DEFAULT_ETH_ENDPOINT: &str =
|
||||
"https://rinkeby.infura.io/v3/00000000000000000000000000000000";
|
||||
@@ -21,6 +17,85 @@ pub(crate) const DEFAULT_ETH_ENDPOINT: &str =
|
||||
pub(crate) const DEFAULT_ETH_PRIVATE_KEY: &str =
|
||||
"0000000000000000000000000000000000000000000000000000000000000001";
|
||||
|
||||
fn long_version() -> String {
|
||||
format!(
|
||||
r#"
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
"#,
|
||||
"Build Timestamp:",
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
"Build Version:",
|
||||
env!("VERGEN_BUILD_SEMVER"),
|
||||
"Commit SHA:",
|
||||
env!("VERGEN_GIT_SHA"),
|
||||
"Commit Date:",
|
||||
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
|
||||
"Commit Branch:",
|
||||
env!("VERGEN_GIT_BRANCH"),
|
||||
"rustc Version:",
|
||||
env!("VERGEN_RUSTC_SEMVER"),
|
||||
"rustc Channel:",
|
||||
env!("VERGEN_RUSTC_CHANNEL"),
|
||||
"cargo Profile:",
|
||||
env!("VERGEN_CARGO_PROFILE"),
|
||||
"Network:",
|
||||
DEFAULT_NETWORK
|
||||
)
|
||||
}
|
||||
|
||||
fn long_version_static() -> &'static str {
|
||||
Box::leak(long_version().into_boxed_str())
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
|
||||
pub(crate) struct Cli {
|
||||
#[clap(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum Commands {
|
||||
/// Initialise a Nym client. Do this first!
|
||||
Init(init::Init),
|
||||
/// Run the Nym client with provided configuration client optionally overriding set parameters
|
||||
Run(run::Run),
|
||||
/// Try to upgrade the client
|
||||
Upgrade(upgrade::Upgrade),
|
||||
}
|
||||
|
||||
// Configuration that can be overridden.
|
||||
pub(crate) struct OverrideConfig {
|
||||
validators: Option<String>,
|
||||
port: Option<u16>,
|
||||
fastmode: bool,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
enabled_credentials_mode: bool,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
eth_private_key: Option<String>,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
eth_endpoint: Option<String>,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Cli) {
|
||||
match &args.command {
|
||||
Commands::Init(m) => init::execute(m).await,
|
||||
Commands::Run(m) => run::execute(m).await,
|
||||
Commands::Upgrade(m) => upgrade::execute(m),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_validators(raw: &str) -> Vec<Url> {
|
||||
raw.split(',')
|
||||
.map(|raw_validator| {
|
||||
@@ -32,41 +107,54 @@ fn parse_validators(raw: &str) -> Vec<Url> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config {
|
||||
if let Some(raw_validators) = matches.value_of("validators") {
|
||||
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
|
||||
if let Some(raw_validators) = args.validators {
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_validator_apis(parse_validators(raw_validators));
|
||||
.set_custom_validator_apis(parse_validators(&raw_validators));
|
||||
}
|
||||
|
||||
if let Some(port) = matches.value_of("port").map(|port| port.parse::<u16>()) {
|
||||
if let Err(err) = port {
|
||||
// if port was overridden, it must be parsable
|
||||
panic!("Invalid port value provided - {:?}", err);
|
||||
if let Some(port) = args.port {
|
||||
config = config.with_port(port);
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "eth"), not(feature = "coconut")))]
|
||||
{
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT.to_string());
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY.to_string());
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
{
|
||||
if args.enabled_credentials_mode {
|
||||
config.get_base_mut().with_disabled_credentials(false)
|
||||
}
|
||||
if let Some(eth_endpoint) = args.eth_endpoint {
|
||||
config.get_base_mut().with_eth_endpoint(eth_endpoint);
|
||||
}
|
||||
if let Some(eth_private_key) = args.eth_private_key {
|
||||
config.get_base_mut().with_eth_private_key(eth_private_key);
|
||||
}
|
||||
config = config.with_port(port.unwrap());
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
if let Some(eth_endpoint) = matches.value_of(ETH_ENDPOINT_ARG_NAME) {
|
||||
config.get_base_mut().with_eth_endpoint(eth_endpoint);
|
||||
} else if !cfg!(feature = "eth") {
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT);
|
||||
}
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
if let Some(eth_private_key) = matches.value_of(ETH_PRIVATE_KEY_ARG_NAME) {
|
||||
config.get_base_mut().with_eth_private_key(eth_private_key);
|
||||
} else if !cfg!(feature = "eth") {
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
|
||||
}
|
||||
|
||||
if matches.is_present(ENABLED_CREDENTIALS_MODE_ARG_NAME) {
|
||||
config.get_base_mut().with_disabled_credentials(false)
|
||||
if args.fastmode {
|
||||
config.get_base_mut().set_high_default_traffic_volume();
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use clap::CommandFactory;
|
||||
|
||||
#[test]
|
||||
fn verify_cli() {
|
||||
Cli::command().debug_assert();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,74 +1,80 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::client::NymClient;
|
||||
use crate::commands::override_config;
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use crate::commands::{
|
||||
ENABLED_CREDENTIALS_MODE_ARG_NAME, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
|
||||
use crate::{
|
||||
client::{config::Config, NymClient},
|
||||
commands::{override_config, OverrideConfig},
|
||||
};
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
|
||||
use clap::Args;
|
||||
use config::NymConfig;
|
||||
use log::*;
|
||||
use version_checker::is_minor_version_compatible;
|
||||
|
||||
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
let app = App::new("run")
|
||||
.about("Run the Nym client with provided configuration client optionally overriding set parameters")
|
||||
.arg(Arg::with_name("id")
|
||||
.long("id")
|
||||
.help("Id of the nym-mixnet-client we want to run.")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
)
|
||||
// the rest of arguments are optional, they are used to override settings in config file
|
||||
.arg(Arg::with_name("config")
|
||||
.long("config")
|
||||
.help("Custom path to the nym-mixnet-client configuration file")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name("provider")
|
||||
.long("provider")
|
||||
.help("Address of the socks5 provider to send messages to.")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name("gateway")
|
||||
.long("gateway")
|
||||
.help("Id of the gateway we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(Arg::with_name("validators")
|
||||
.long("validators")
|
||||
.help("Comma separated list of rest endpoints of the validators")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(Arg::with_name("port")
|
||||
.short("p")
|
||||
.long("port")
|
||||
.help("Port for the socket to listen on")
|
||||
.takes_value(true)
|
||||
);
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let app = app
|
||||
.arg(
|
||||
Arg::with_name(ENABLED_CREDENTIALS_MODE_ARG_NAME)
|
||||
.long(ENABLED_CREDENTIALS_MODE_ARG_NAME)
|
||||
.help("Set this client to work in a disabled credentials mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
|
||||
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
|
||||
)
|
||||
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
|
||||
.long(ETH_ENDPOINT_ARG_NAME)
|
||||
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.long(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true));
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct Run {
|
||||
/// Id of the nym-mixnet-client we want to run.
|
||||
#[clap(long)]
|
||||
id: String,
|
||||
|
||||
app
|
||||
/// Custom path to the nym-mixnet-client configuration file
|
||||
#[clap(long)]
|
||||
config: Option<String>,
|
||||
|
||||
/// Address of the socks5 provider to send messages to.
|
||||
#[clap(long)]
|
||||
provider: Option<String>,
|
||||
|
||||
/// Id of the gateway we want to connect to. If overridden, it is user's responsibility to
|
||||
/// ensure prior registration happened
|
||||
#[clap(long)]
|
||||
gateway: Option<String>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the validators
|
||||
#[clap(long)]
|
||||
validators: Option<String>,
|
||||
|
||||
/// Port for the socket to listen on
|
||||
#[clap(short, long)]
|
||||
port: Option<u16>,
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement. If this value is set, --eth-endpoint and
|
||||
/// --eth-private-key don't need to be set.
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(long, conflicts_with_all = &["eth-endpoint", "eth-private-key"])]
|
||||
enabled_credentials_mode: bool,
|
||||
|
||||
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
|
||||
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(long)]
|
||||
eth_endpoint: Option<String>,
|
||||
|
||||
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
|
||||
/// want to set this value, use --enabled-credentials-mode instead
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
#[clap(long)]
|
||||
eth_private_key: Option<String>,
|
||||
}
|
||||
|
||||
impl From<Run> for OverrideConfig {
|
||||
fn from(run_config: Run) -> Self {
|
||||
OverrideConfig {
|
||||
validators: run_config.validators,
|
||||
port: run_config.port,
|
||||
fastmode: false,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
enabled_credentials_mode: run_config.enabled_credentials_mode,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
eth_private_key: run_config.eth_private_key,
|
||||
|
||||
#[cfg(all(feature = "eth", not(feature = "coconut")))]
|
||||
eth_endpoint: run_config.eth_endpoint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this only checks compatibility between config the binary. It does not take into consideration
|
||||
@@ -76,8 +82,13 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
fn version_check(cfg: &Config) -> bool {
|
||||
let binary_version = env!("CARGO_PKG_VERSION");
|
||||
let config_version = cfg.get_base().get_version();
|
||||
if binary_version != config_version {
|
||||
warn!("The mixnode binary has different version than what is specified in config file! {} and {}", binary_version, config_version);
|
||||
if binary_version == config_version {
|
||||
true
|
||||
} else {
|
||||
warn!(
|
||||
"The mixnode binary has different version than what is specified in config file! {} and {}",
|
||||
binary_version, config_version
|
||||
);
|
||||
if is_minor_version_compatible(binary_version, config_version) {
|
||||
info!("but they are still semver compatible. However, consider running the `upgrade` command");
|
||||
true
|
||||
@@ -85,13 +96,11 @@ fn version_check(cfg: &Config) -> bool {
|
||||
error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again");
|
||||
false
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn execute(matches: ArgMatches<'static>) {
|
||||
let id = matches.value_of("id").unwrap();
|
||||
pub(crate) async fn execute(args: &Run) {
|
||||
let id = &args.id;
|
||||
|
||||
let mut config = match Config::load_from_file(Some(id)) {
|
||||
Ok(cfg) => cfg,
|
||||
@@ -101,7 +110,8 @@ pub async fn execute(matches: ArgMatches<'static>) {
|
||||
}
|
||||
};
|
||||
|
||||
config = override_config(config, &matches);
|
||||
let override_config_fields = OverrideConfig::from(args.clone());
|
||||
config = override_config(config, override_config_fields);
|
||||
|
||||
if !version_check(&config) {
|
||||
error!("failed the local version check");
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::{Config, MISSING_VALUE};
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::defaults::default_api_endpoints;
|
||||
use config::NymConfig;
|
||||
use std::fmt::Display;
|
||||
use std::process;
|
||||
|
||||
use config::{defaults::default_api_endpoints, NymConfig};
|
||||
use version_checker::Version;
|
||||
|
||||
use clap::Args;
|
||||
use std::{fmt::Display, process};
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn fail_upgrade<D1: Display, D2: Display>(from_version: D1, to_version: D2) -> ! {
|
||||
print_failed_upgrade(from_version, to_version);
|
||||
@@ -49,14 +49,11 @@ fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> !
|
||||
process::exit(1)
|
||||
}
|
||||
|
||||
pub fn command_args<'a, 'b>() -> App<'a, 'b> {
|
||||
App::new("upgrade").about("Try to upgrade the client").arg(
|
||||
Arg::with_name("id")
|
||||
.long("id")
|
||||
.help("Id of the nym-client we want to upgrade")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct Upgrade {
|
||||
/// Id of the nym-client we want to upgrade
|
||||
#[clap(long)]
|
||||
id: String,
|
||||
}
|
||||
|
||||
fn parse_config_version(config: &Config) -> Version {
|
||||
@@ -95,7 +92,7 @@ fn parse_package_version() -> Version {
|
||||
|
||||
fn minor_0_12_upgrade(
|
||||
mut config: Config,
|
||||
_matches: &ArgMatches<'_>,
|
||||
_args: &Upgrade,
|
||||
config_version: &Version,
|
||||
package_version: &Version,
|
||||
) -> Config {
|
||||
@@ -131,30 +128,30 @@ fn minor_0_12_upgrade(
|
||||
config
|
||||
}
|
||||
|
||||
fn do_upgrade(mut config: Config, matches: &ArgMatches<'_>, package_version: Version) {
|
||||
fn do_upgrade(mut config: Config, args: &Upgrade, package_version: &Version) {
|
||||
loop {
|
||||
let config_version = parse_config_version(&config);
|
||||
|
||||
if config_version == package_version {
|
||||
if &config_version == package_version {
|
||||
println!("You're using the most recent version!");
|
||||
return;
|
||||
}
|
||||
|
||||
config = match config_version.major {
|
||||
0 => match config_version.minor {
|
||||
9 | 10 => outdated_upgrade(&config_version, &package_version),
|
||||
11 => minor_0_12_upgrade(config, matches, &config_version, &package_version),
|
||||
_ => unsupported_upgrade(&config_version, &package_version),
|
||||
9 | 10 => outdated_upgrade(&config_version, package_version),
|
||||
11 => minor_0_12_upgrade(config, args, &config_version, package_version),
|
||||
_ => unsupported_upgrade(&config_version, package_version),
|
||||
},
|
||||
_ => unsupported_upgrade(&config_version, &package_version),
|
||||
_ => unsupported_upgrade(&config_version, package_version),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute(matches: &ArgMatches<'_>) {
|
||||
pub(crate) fn execute(args: &Upgrade) {
|
||||
let package_version = parse_package_version();
|
||||
|
||||
let id = matches.value_of("id").unwrap();
|
||||
let id = &args.id;
|
||||
|
||||
let existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| {
|
||||
eprintln!("failed to load existing config file! - {:?}", err);
|
||||
@@ -167,5 +164,5 @@ pub fn execute(matches: &ArgMatches<'_>) {
|
||||
}
|
||||
|
||||
// here be upgrade path to 0.9.X and beyond based on version number from config
|
||||
do_upgrade(existing_config, matches, package_version)
|
||||
do_upgrade(existing_config, args, &package_version)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{crate_version, App, ArgMatches};
|
||||
use network_defaults::DEFAULT_NETWORK;
|
||||
use clap::{crate_version, Parser};
|
||||
|
||||
pub mod client;
|
||||
mod commands;
|
||||
@@ -14,30 +13,8 @@ async fn main() {
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
|
||||
let arg_matches = App::new("Nym Socks5 Proxy")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.author("Nymtech")
|
||||
.long_version(&*long_version())
|
||||
.about("A Socks5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address")
|
||||
.subcommand(commands::init::command_args())
|
||||
.subcommand(commands::run::command_args())
|
||||
.subcommand(commands::upgrade::command_args())
|
||||
.get_matches();
|
||||
|
||||
execute(arg_matches).await;
|
||||
}
|
||||
|
||||
async fn execute(matches: ArgMatches<'static>) {
|
||||
match matches.subcommand() {
|
||||
("init", Some(m)) => commands::init::execute(m.clone()).await,
|
||||
("run", Some(m)) => commands::run::execute(m.clone()).await,
|
||||
("upgrade", Some(m)) => commands::upgrade::execute(m),
|
||||
_ => println!("{}", usage()),
|
||||
}
|
||||
}
|
||||
|
||||
fn usage() -> &'static str {
|
||||
"usage: --help to see available options.\n\n"
|
||||
let args = commands::Cli::parse();
|
||||
commands::execute(&args).await;
|
||||
}
|
||||
|
||||
fn banner() -> String {
|
||||
@@ -57,40 +34,6 @@ fn banner() -> String {
|
||||
)
|
||||
}
|
||||
|
||||
fn long_version() -> String {
|
||||
format!(
|
||||
r#"
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
"#,
|
||||
"Build Timestamp:",
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
"Build Version:",
|
||||
env!("VERGEN_BUILD_SEMVER"),
|
||||
"Commit SHA:",
|
||||
env!("VERGEN_GIT_SHA"),
|
||||
"Commit Date:",
|
||||
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
|
||||
"Commit Branch:",
|
||||
env!("VERGEN_GIT_BRANCH"),
|
||||
"rustc Version:",
|
||||
env!("VERGEN_RUSTC_SEMVER"),
|
||||
"rustc Channel:",
|
||||
env!("VERGEN_RUSTC_CHANNEL"),
|
||||
"cargo Profile:",
|
||||
env!("VERGEN_CARGO_PROFILE"),
|
||||
"Network:",
|
||||
DEFAULT_NETWORK
|
||||
)
|
||||
}
|
||||
|
||||
fn setup_logging() {
|
||||
let mut log_builder = pretty_env_logger::formatted_timed_builder();
|
||||
if let Ok(s) = ::std::env::var("RUST_LOG") {
|
||||
|
||||
@@ -1 +1 @@
|
||||
15.0.1
|
||||
16
|
||||
@@ -31,9 +31,12 @@
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-mocha": "^10.0.3",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"expect": "^28.1.3",
|
||||
"mocha": "^10.0.0",
|
||||
"prettier": "^2.5.1",
|
||||
"typedoc": "^0.22.13",
|
||||
"typescript": "^4.1.3"
|
||||
"ts-mocha": "^10.0.0",
|
||||
"typescript": "^4.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cosmjs/cosmwasm-stargate": "^0.28.0",
|
||||
|
||||
@@ -64,23 +64,20 @@ export default class ValidatorClient implements INymClient {
|
||||
|
||||
readonly vestingContract: string;
|
||||
|
||||
readonly mainnetDenom = "unym";
|
||||
readonly mainnetDenom = 'unym';
|
||||
|
||||
readonly mainnetPrefix = "n";
|
||||
readonly mainnetPrefix = 'n';
|
||||
|
||||
private constructor(
|
||||
client: SigningClient | QueryClient,
|
||||
prefix: string,
|
||||
mixnetContract: string,
|
||||
vestingContract: string
|
||||
vestingContract: string,
|
||||
denom: string,
|
||||
) {
|
||||
this.client = client;
|
||||
this.prefix = prefix;
|
||||
if (prefix == this.mainnetPrefix) {
|
||||
this.denom = this.mainnetDenom;
|
||||
} else {
|
||||
this.denom = `u${prefix}`;
|
||||
}
|
||||
this.denom = `u${denom}`;
|
||||
|
||||
this.mixnetContract = mixnetContract;
|
||||
this.vestingContract = vestingContract;
|
||||
@@ -93,11 +90,12 @@ export default class ValidatorClient implements INymClient {
|
||||
prefix: string,
|
||||
mixnetContract: string,
|
||||
vestingContract: string,
|
||||
denom: string,
|
||||
): Promise<ValidatorClient> {
|
||||
const wallet = await ValidatorClient.buildWallet(mnemonic, prefix);
|
||||
|
||||
const signingClient = await SigningClient.connectWithNymSigner(wallet, nymdUrl, validatorApiUrl, prefix);
|
||||
return new ValidatorClient(signingClient, prefix, mixnetContract, vestingContract);
|
||||
const signingClient = await SigningClient.connectWithNymSigner(wallet, nymdUrl, validatorApiUrl, prefix, denom);
|
||||
return new ValidatorClient(signingClient, prefix, mixnetContract, vestingContract, denom);
|
||||
}
|
||||
|
||||
static async connectForQuery(
|
||||
@@ -106,9 +104,10 @@ export default class ValidatorClient implements INymClient {
|
||||
prefix: string,
|
||||
mixnetContract: string,
|
||||
vestingContract: string,
|
||||
denom: string,
|
||||
): Promise<ValidatorClient> {
|
||||
const queryClient = await QueryClient.connectWithNym(nymdUrl, validatorApiUrl);
|
||||
return new ValidatorClient(queryClient, prefix, mixnetContract, vestingContract);
|
||||
return new ValidatorClient(queryClient, prefix, mixnetContract, vestingContract, denom);
|
||||
}
|
||||
|
||||
public get address(): string {
|
||||
@@ -457,4 +456,4 @@ export default class ValidatorClient implements INymClient {
|
||||
this.assertSigning();
|
||||
return (this.client as ISigningClient).updateContractStateParams(this.mixnetContract, newParams, fee, memo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,10 +221,12 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
|
||||
nymdUrl: string,
|
||||
validatorApiUrl: string,
|
||||
prefix: string,
|
||||
denom: string,
|
||||
): Promise<SigningClient> {
|
||||
const [{ address }] = await wallet.getAccounts();
|
||||
const signerOptions: SigningCosmWasmClientOptions = {
|
||||
gasPrice: nymGasPrice(prefix),
|
||||
prefix,
|
||||
gasPrice: nymGasPrice(denom),
|
||||
};
|
||||
const tmClient = await Tendermint34Client.connect(nymdUrl);
|
||||
return new SigningClient(address, validatorApiUrl, tmClient, wallet, signerOptions);
|
||||
|
||||
@@ -7,13 +7,12 @@ const mainnetDenom = 'nym';
|
||||
export function nymGasPrice(prefix: string): GasPrice {
|
||||
if (typeof prefix === 'string') {
|
||||
if (prefix === mainnetPrefix) {
|
||||
prefix = mainnetDenom;
|
||||
return GasPrice.fromString(`0.025u${mainnetDenom}`);
|
||||
}
|
||||
return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here.
|
||||
}
|
||||
else {
|
||||
throw new Error(`${prefix} is not of type string`);
|
||||
}
|
||||
|
||||
throw new Error(`${prefix} is not of type string`);
|
||||
}
|
||||
|
||||
export const downloadWasm = async (url: string): Promise<Uint8Array> => {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import ValidatorClient from '../../dist';
|
||||
import expect from 'expect';
|
||||
|
||||
describe('Query: balances', () => {
|
||||
it('can query for an account balance', async () => {
|
||||
const client = await ValidatorClient.connectForQuery(
|
||||
'https://rpc.nyx.nodes.guru/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym');
|
||||
const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy');
|
||||
expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0);
|
||||
}).timeout(5000);
|
||||
})
|
||||
@@ -0,0 +1,14 @@
|
||||
import ValidatorClient from '../../dist';
|
||||
import expect from 'expect';
|
||||
|
||||
// TODO: implement for QA with .env for mnemonics
|
||||
// describe('Sign: send', () => {
|
||||
// it('can send tokens', async () => {
|
||||
// const client = await ValidatorClient.connect(
|
||||
// '<ADD MNEMONIC HERE>',
|
||||
// 'https://rpc.nyx.nodes.guru/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym');
|
||||
// await client.send('<ADD ADDRESS HERE>')
|
||||
// const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy');
|
||||
// expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0);
|
||||
// }).timeout(5000);
|
||||
// })
|
||||
@@ -5,7 +5,8 @@
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist"
|
||||
"outDir": "./dist",
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"typedocOptions": {
|
||||
"entryPoints": [
|
||||
|
||||
@@ -35,7 +35,7 @@ validator-api-requests = { path = "../../../validator-api/validator-api-requests
|
||||
async-trait = { version = "0.1.51", optional = true }
|
||||
bip39 = { version = "1", features = ["rand"], optional = true }
|
||||
config = { path = "../../config", optional = true }
|
||||
cosmrs = { version = "0.7.0", features = ["rpc", "bip32", "cosmwasm"], optional = true}
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true}
|
||||
prost = { version = "0.10", default-features = false, optional = true }
|
||||
flate2 = { version = "1.0.20", optional = true }
|
||||
sha2 = { version = "0.9.5", optional = true }
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::{validator_api, ValidatorClientError};
|
||||
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
|
||||
use url::Url;
|
||||
use validator_api_requests::coconut::{
|
||||
BlindSignRequestBody, BlindedSignatureResponse, ExecuteReleaseFundsRequestBody,
|
||||
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse,
|
||||
ProposeReleaseFundsRequestBody, ProposeReleaseFundsResponse, VerificationKeyResponse,
|
||||
VerifyCredentialBody, VerifyCredentialResponse,
|
||||
};
|
||||
@@ -734,6 +734,10 @@ impl ApiClient {
|
||||
Ok(self.validator_api.get_coconut_verification_key().await?)
|
||||
}
|
||||
|
||||
pub async fn get_cosmos_address(&self) -> Result<CosmosAddressResponse, ValidatorClientError> {
|
||||
Ok(self.validator_api.get_cosmos_address().await?)
|
||||
}
|
||||
|
||||
pub async fn verify_bandwidth_credential(
|
||||
&self,
|
||||
request_body: &VerifyCredentialBody,
|
||||
@@ -753,14 +757,4 @@ impl ApiClient {
|
||||
.propose_release_funds(request_body)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn execute_release_funds(
|
||||
&self,
|
||||
request_body: &ExecuteReleaseFundsRequestBody,
|
||||
) -> Result<(), ValidatorClientError> {
|
||||
Ok(self
|
||||
.validator_api
|
||||
.execute_release_funds(request_body)
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ use cosmrs::rpc::{self, HttpClient, Order};
|
||||
use cosmrs::tendermint::abci::Code as AbciCode;
|
||||
use cosmrs::tendermint::abci::Transaction;
|
||||
use cosmrs::tendermint::{abci, block, chain};
|
||||
use cosmrs::{tx, AccountId, Coin as CosmosCoin, Denom, Tx};
|
||||
use cosmrs::{tx, AccountId, Coin as CosmosCoin, Tx};
|
||||
use prost::Message;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
@@ -121,7 +121,7 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
async fn get_balance(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
search_denom: Denom,
|
||||
search_denom: String,
|
||||
) -> Result<Option<Coin>, NymdError> {
|
||||
let path = Some("/cosmos.bank.v1beta1.Query/Balance".parse().unwrap());
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ use crate::nymd::{Coin, GasAdjustable, GasPrice, TxResponse};
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::bank::MsgSend;
|
||||
use cosmrs::distribution::MsgWithdrawDelegatorReward;
|
||||
use cosmrs::feegrant::{
|
||||
AllowedMsgAllowance, BasicAllowance, MsgGrantAllowance, MsgRevokeAllowance,
|
||||
};
|
||||
use cosmrs::proto::cosmos::tx::signing::v1beta1::SignMode;
|
||||
use cosmrs::rpc::endpoint::broadcast;
|
||||
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
|
||||
@@ -23,7 +26,7 @@ use serde::Serialize;
|
||||
use sha2::Digest;
|
||||
use sha2::Sha256;
|
||||
use std::convert::TryInto;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
const DEFAULT_BROADCAST_POLLING_RATE: Duration = Duration::from_secs(4);
|
||||
const DEFAULT_BROADCAST_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
@@ -420,6 +423,63 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.check_response()
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn grant_allowance(
|
||||
&self,
|
||||
granter: &AccountId,
|
||||
grantee: &AccountId,
|
||||
spend_limit: Vec<Coin>,
|
||||
expiration: Option<SystemTime>,
|
||||
allowed_messages: Vec<String>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<TxResponse, NymdError> {
|
||||
let basic_allowance = BasicAllowance {
|
||||
spend_limit: spend_limit.into_iter().map(Into::into).collect(),
|
||||
expiration,
|
||||
}
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("BasicAllowance".to_owned()))?;
|
||||
|
||||
let allowed_msg_allowance = AllowedMsgAllowance {
|
||||
allowance: Some(basic_allowance),
|
||||
allowed_messages,
|
||||
}
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("AllowedMsgAllowance".to_owned()))?;
|
||||
|
||||
let grant_allowance_msg = MsgGrantAllowance {
|
||||
granter: granter.to_owned(),
|
||||
grantee: grantee.to_owned(),
|
||||
allowance: Some(allowed_msg_allowance),
|
||||
}
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgGrantAllowance".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast(granter, vec![grant_allowance_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()
|
||||
}
|
||||
|
||||
async fn revoke_allowance(
|
||||
&self,
|
||||
granter: &AccountId,
|
||||
grantee: &AccountId,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<TxResponse, NymdError> {
|
||||
let revoke_allowance_msg = MsgRevokeAllowance {
|
||||
granter: granter.to_owned(),
|
||||
grantee: grantee.to_owned(),
|
||||
}
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgRevokeAllowance".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast(granter, vec![revoke_allowance_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()
|
||||
}
|
||||
|
||||
async fn delegate_tokens(
|
||||
&self,
|
||||
delegator_address: &AccountId,
|
||||
@@ -514,10 +574,10 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
let fee = match fee {
|
||||
Fee::Manual(fee) => fee,
|
||||
Fee::Auto(multiplier) => auto_fee(multiplier).await?,
|
||||
Fee::PayerGranterAuto(multiplier, payer, granter) => {
|
||||
let mut fee = auto_fee(multiplier).await?;
|
||||
fee.payer = payer;
|
||||
fee.granter = granter;
|
||||
Fee::PayerGranterAuto(auto_feegrant) => {
|
||||
let mut fee = auto_fee(auto_feegrant.gas_adjustment).await?;
|
||||
fee.payer = auto_feegrant.payer;
|
||||
fee.granter = auto_feegrant.granter;
|
||||
fee
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nymd::Coin;
|
||||
use crate::nymd::Gas;
|
||||
use cosmrs::{tx, AccountId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
pub mod gas_price;
|
||||
|
||||
@@ -11,11 +13,74 @@ pub type GasAdjustment = f32;
|
||||
|
||||
pub const DEFAULT_SIMULATED_GAS_MULTIPLIER: GasAdjustment = 1.3;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AutoFeeGrant {
|
||||
pub gas_adjustment: Option<GasAdjustment>,
|
||||
pub payer: Option<AccountId>,
|
||||
pub granter: Option<AccountId>,
|
||||
}
|
||||
|
||||
impl Display for AutoFeeGrant {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(gas_adjustment) = self.gas_adjustment {
|
||||
write!(f, "Feegrant in auto mode with {gas_adjustment} simulated multiplier with {:?} payer and {:?} granter", self.payer, self.granter)
|
||||
} else {
|
||||
write!(f, "Feegrant in auto mode with no custom simulated multiplier with {:?} payer and {:?} granter", self.payer, self.granter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Fee {
|
||||
Manual(#[serde(with = "sealed::TxFee")] tx::Fee),
|
||||
Auto(Option<GasAdjustment>),
|
||||
PayerGranterAuto(Option<GasAdjustment>, Option<AccountId>, Option<AccountId>),
|
||||
PayerGranterAuto(AutoFeeGrant),
|
||||
}
|
||||
|
||||
impl Display for Fee {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Fee::Manual(fee) => {
|
||||
write!(f, "Fee in manual mode with ")?;
|
||||
for fee in &fee.amount {
|
||||
write!(f, "{}{} paid in fees, ", fee.amount, fee.denom)?;
|
||||
}
|
||||
write!(f, "{} set as gas limit, ", fee.gas_limit)?;
|
||||
if let Some(payer) = &fee.payer {
|
||||
write!(f, "{payer} set as payer, ")?;
|
||||
}
|
||||
if let Some(granter) = &fee.granter {
|
||||
write!(f, "{granter} set as granter")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Fee::Auto(Some(multiplier)) => {
|
||||
write!(f, "Fee in auto mode with {multiplier} simulated multiplier")
|
||||
}
|
||||
Fee::Auto(None) => write!(f, "Fee in auto mode with no custom simulated multiplier"),
|
||||
Fee::PayerGranterAuto(auto_feegrant) => write!(f, "{}", auto_feegrant),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Fee {
|
||||
pub fn new_payer_granter_auto(
|
||||
gas_adjustment: Option<GasAdjustment>,
|
||||
payer: Option<AccountId>,
|
||||
granter: Option<AccountId>,
|
||||
) -> Self {
|
||||
Fee::PayerGranterAuto(AutoFeeGrant {
|
||||
gas_adjustment,
|
||||
payer,
|
||||
granter,
|
||||
})
|
||||
}
|
||||
pub fn try_get_manual_amount(&self) -> Option<Vec<Coin>> {
|
||||
match self {
|
||||
Fee::Manual(tx_fee) => Some(tx_fee.amount.iter().cloned().map(Into::into).collect()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tx::Fee> for Fee {
|
||||
|
||||
@@ -26,6 +26,7 @@ use mixnet_contract_common::{
|
||||
};
|
||||
use serde::Serialize;
|
||||
use std::convert::TryInto;
|
||||
use std::time::SystemTime;
|
||||
use vesting_contract_common::ExecuteMsg as VestingExecuteMsg;
|
||||
use vesting_contract_common::QueryMsg as VestingQueryMsg;
|
||||
|
||||
@@ -388,7 +389,7 @@ impl<C> NymdClient<C> {
|
||||
pub async fn get_balance(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
denom: Denom,
|
||||
denom: String,
|
||||
) -> Result<Option<Coin>, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
@@ -845,6 +846,49 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Grant a fee allowance from one address to another
|
||||
pub async fn grant_allowance(
|
||||
&self,
|
||||
grantee: &AccountId,
|
||||
spend_limit: Vec<Coin>,
|
||||
expiration: Option<SystemTime>,
|
||||
allowed_messages: Vec<String>,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<TxResponse, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
|
||||
self.client
|
||||
.grant_allowance(
|
||||
self.address(),
|
||||
grantee,
|
||||
spend_limit,
|
||||
expiration,
|
||||
allowed_messages,
|
||||
fee,
|
||||
memo,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Revoke a fee allowance from one address to another
|
||||
pub async fn revoke_allowance(
|
||||
&self,
|
||||
grantee: &AccountId,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<TxResponse, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
|
||||
self.client
|
||||
.revoke_allowance(self.address(), grantee, fee, memo)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn execute<M>(
|
||||
&self,
|
||||
contract_address: &AccountId,
|
||||
|
||||
@@ -9,8 +9,8 @@ use std::collections::HashMap;
|
||||
use url::Url;
|
||||
use validator_api_requests::coconut::{
|
||||
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse,
|
||||
ExecuteReleaseFundsRequestBody, ProposeReleaseFundsRequestBody, ProposeReleaseFundsResponse,
|
||||
VerificationKeyResponse, VerifyCredentialBody, VerifyCredentialResponse,
|
||||
ProposeReleaseFundsRequestBody, ProposeReleaseFundsResponse, VerificationKeyResponse,
|
||||
VerifyCredentialBody, VerifyCredentialResponse,
|
||||
};
|
||||
use validator_api_requests::models::{
|
||||
CoreNodeStatusResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
|
||||
@@ -422,23 +422,6 @@ impl Client {
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn execute_release_funds(
|
||||
&self,
|
||||
request_body: &ExecuteReleaseFundsRequestBody,
|
||||
) -> Result<(), ValidatorAPIError> {
|
||||
self.post_validator_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::COCONUT_ROUTES,
|
||||
routes::BANDWIDTH,
|
||||
routes::COCONUT_EXECUTE_RELEASE_FUNDS,
|
||||
],
|
||||
NO_PARAMS,
|
||||
request_body,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
// utility function that should solve the double slash problem in validator API forever.
|
||||
|
||||
@@ -20,7 +20,6 @@ pub const COCONUT_VERIFICATION_KEY: &str = "verification-key";
|
||||
pub const COCONUT_COSMOS_ADDRESS: &str = "cosmos-address";
|
||||
pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential";
|
||||
pub const COCONUT_PROPOSE_RELEASE_FUNDS: &str = "propose-release-funds";
|
||||
pub const COCONUT_EXECUTE_RELEASE_FUNDS: &str = "execute-release-funds";
|
||||
|
||||
pub const STATUS_ROUTES: &str = "status";
|
||||
pub const MIXNODE: &str = "mixnode";
|
||||
|
||||
@@ -3,6 +3,7 @@ name = "mixnet-contract-common"
|
||||
version = "0.1.0"
|
||||
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.62"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ pub struct Delegation {
|
||||
pub node_identity: IdentityKey,
|
||||
pub amount: Coin,
|
||||
pub block_height: u64,
|
||||
pub proxy: Option<Addr>, // proxy address used to delegate the funds on behalf of anouther address
|
||||
pub proxy: Option<Addr>, // proxy address used to delegate the funds on behalf of another address
|
||||
}
|
||||
|
||||
impl Eq for Delegation {}
|
||||
|
||||
@@ -37,6 +37,16 @@ pub enum DelegationEvent {
|
||||
Undelegate(PendingUndelegate),
|
||||
}
|
||||
|
||||
impl DelegationEvent {
|
||||
pub fn delegation_amount(&self) -> Option<Coin> {
|
||||
match self {
|
||||
DelegationEvent::Delegate(delegation) => Some(delegation.amount.clone()),
|
||||
// I think it would be nice to also expose an amount here to know how much we're undelegating
|
||||
DelegationEvent::Undelegate(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct PendingUndelegate {
|
||||
mix_identity: IdentityKey,
|
||||
|
||||
@@ -28,8 +28,8 @@ pub enum Period {
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct PledgeData {
|
||||
amount: Coin,
|
||||
block_time: Timestamp,
|
||||
pub amount: Coin,
|
||||
pub block_time: Timestamp,
|
||||
}
|
||||
|
||||
impl PledgeData {
|
||||
@@ -48,9 +48,9 @@ impl PledgeData {
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct OriginalVestingResponse {
|
||||
amount: Coin,
|
||||
number_of_periods: usize,
|
||||
period_duration: u64,
|
||||
pub amount: Coin,
|
||||
pub number_of_periods: usize,
|
||||
pub period_duration: u64,
|
||||
}
|
||||
|
||||
impl OriginalVestingResponse {
|
||||
|
||||
@@ -7,7 +7,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
|
||||
cosmrs = { version = "0.7.0", optional = true }
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", optional = true }
|
||||
thiserror = "1.0"
|
||||
url = "2.2"
|
||||
|
||||
|
||||
@@ -267,7 +267,7 @@ impl DenomDetails {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, Hash, Clone, PartialEq, Eq)]
|
||||
pub struct DenomDetailsOwned {
|
||||
pub base: String,
|
||||
pub display: String,
|
||||
|
||||
@@ -20,7 +20,7 @@ url = "2.2"
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
cosmwasm-std = "1.0.0-beta8"
|
||||
cosmrs = "0.7.0"
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = [
|
||||
"nymd-client",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::currency::{CurrencyDenom, MajorCurrencyAmount};
|
||||
use crate::currency::{CurrencyDenom, DecCoin};
|
||||
use config::defaults::DenomDetails;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -9,17 +10,20 @@ use serde::{Deserialize, Serialize};
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
pub struct Account {
|
||||
pub contract_address: String,
|
||||
pub client_address: String,
|
||||
pub denom: CurrencyDenom,
|
||||
pub base_mix_denom: String,
|
||||
|
||||
// this should get refactored to just use a String, but for now it's fine as it reduces headache
|
||||
// for others
|
||||
pub display_mix_denom: CurrencyDenom,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub fn new(contract_address: String, client_address: String, denom: CurrencyDenom) -> Self {
|
||||
pub fn new(client_address: String, mix_denom: DenomDetails) -> Self {
|
||||
Account {
|
||||
contract_address,
|
||||
client_address,
|
||||
denom,
|
||||
base_mix_denom: mix_denom.base.to_owned(),
|
||||
display_mix_denom: mix_denom.display.parse().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,6 +57,15 @@ pub struct AccountEntry {
|
||||
)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Balance {
|
||||
pub amount: MajorCurrencyAmount,
|
||||
pub amount: DecCoin,
|
||||
pub printable_balance: String,
|
||||
}
|
||||
|
||||
impl Balance {
|
||||
pub fn new(amount: DecCoin) -> Self {
|
||||
Balance {
|
||||
printable_balance: amount.to_string(),
|
||||
amount,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
use crate::error::TypesError;
|
||||
use cosmrs::Denom as CosmosDenom;
|
||||
use cosmwasm_std::Coin as CosmWasmCoin;
|
||||
use config::defaults::all::Network;
|
||||
use config::defaults::{DenomDetails, DenomDetailsOwned};
|
||||
use cosmwasm_std::Fraction;
|
||||
use cosmwasm_std::{Decimal, Uint128};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::{Add, Mul};
|
||||
use std::str::FromStr;
|
||||
use strum::{Display, EnumString, EnumVariantNames};
|
||||
use validator_client::nymd::{Coin, CosmosCoin};
|
||||
use validator_client::nymd::Coin;
|
||||
|
||||
#[cfg(feature = "generate-ts")]
|
||||
use ts_rs::{Dependency, TS};
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/CurrencyDenom.ts")
|
||||
)]
|
||||
#[cfg_attr(feature = "generate-ts", ts(rename_all = "UPPERCASE"))]
|
||||
#[cfg_attr(feature = "generate-ts", ts(rename_all = "lowercase"))]
|
||||
#[derive(
|
||||
Display,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Clone,
|
||||
@@ -29,10 +34,12 @@ use validator_client::nymd::{Coin, CosmosCoin};
|
||||
Eq,
|
||||
JsonSchema,
|
||||
)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
#[strum(serialize_all = "UPPERCASE")]
|
||||
// TODO: this shouldn't be an enum...
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum CurrencyDenom {
|
||||
#[strum(ascii_case_insensitive)]
|
||||
#[default]
|
||||
Unknown,
|
||||
#[strum(ascii_case_insensitive)]
|
||||
Nym,
|
||||
#[strum(ascii_case_insensitive)]
|
||||
@@ -43,448 +50,469 @@ pub enum CurrencyDenom {
|
||||
Nyxt,
|
||||
}
|
||||
|
||||
impl CurrencyDenom {
|
||||
pub fn parse(value: &str) -> Result<CurrencyDenom, TypesError> {
|
||||
let mut denom = value.to_string();
|
||||
if denom.starts_with('u') {
|
||||
denom = denom[1..].to_string();
|
||||
pub type Denom = String;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct RegisteredCoins(HashMap<Denom, CoinMetadata>);
|
||||
|
||||
impl RegisteredCoins {
|
||||
pub fn default_denoms(network: Network) -> Self {
|
||||
let mut network_coins = HashMap::new();
|
||||
network_coins.insert(network.mix_denom().base, network.mix_denom().into());
|
||||
network_coins.insert(network.stake_denom().base, network.stake_denom().into());
|
||||
RegisteredCoins(network_coins)
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, denom: Denom, metadata: CoinMetadata) -> Option<CoinMetadata> {
|
||||
self.0.insert(denom, metadata)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, denom: &Denom) -> Option<CoinMetadata> {
|
||||
self.0.remove(denom)
|
||||
}
|
||||
|
||||
pub fn attempt_convert_to_base_coin(&self, coin: DecCoin) -> Result<Coin, TypesError> {
|
||||
// check if this is already in the base denom
|
||||
if self.0.contains_key(&coin.denom) {
|
||||
// if we're converting a base DecCoin it CANNOT fail, unless somebody is providing
|
||||
// bullshit data on purpose : )
|
||||
return coin.try_into();
|
||||
} else {
|
||||
// TODO: this kinda suggests we may need a better data structure
|
||||
for registered_coin in self.0.values() {
|
||||
if let Some(exponent) = registered_coin.get_exponent(&coin.denom) {
|
||||
let amount = try_convert_decimal_to_u128(coin.try_scale_up_value(exponent)?)?;
|
||||
return Ok(Coin::new(amount, ®istered_coin.base));
|
||||
}
|
||||
}
|
||||
}
|
||||
match CurrencyDenom::from_str(&denom) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(_e) => Err(TypesError::InvalidDenom(value.to_string())),
|
||||
Err(TypesError::UnknownCoinDenom(coin.denom))
|
||||
}
|
||||
|
||||
pub fn attempt_convert_to_display_dec_coin(&self, coin: Coin) -> Result<DecCoin, TypesError> {
|
||||
for registered_coin in self.0.values() {
|
||||
if let Some(exponent) = registered_coin.get_exponent(&coin.denom) {
|
||||
// if this fails it means we haven't registered our display denom which honestly should never be the case
|
||||
// unless somebody is rocking their own custom network
|
||||
let display_exponent = registered_coin
|
||||
.get_exponent(®istered_coin.display)
|
||||
.ok_or_else(|| TypesError::UnknownCoinDenom(coin.denom.clone()))?;
|
||||
|
||||
return match exponent.cmp(&display_exponent) {
|
||||
Ordering::Greater => {
|
||||
// we need to scale up, unlikely to ever be needed but included for completion sake,
|
||||
// for example if we decided to created knym with exponent 9 and wanted to convert to nym with exponent 6
|
||||
Ok(DecCoin::new_scaled_up(
|
||||
coin.amount,
|
||||
®istered_coin.display,
|
||||
exponent - display_exponent,
|
||||
)?)
|
||||
}
|
||||
// we're already in the display denom
|
||||
Ordering::Equal => Ok(coin.into()),
|
||||
Ordering::Less => {
|
||||
// we need to scale down, the most common case, for example we're in base unym with exponent 0 and want to convert to nym with exponent 6
|
||||
Ok(DecCoin::new_scaled_down(
|
||||
coin.amount,
|
||||
®istered_coin.display,
|
||||
display_exponent - exponent,
|
||||
)?)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Err(TypesError::UnknownCoinDenom(coin.denom))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<CosmosDenom> for CurrencyDenom {
|
||||
type Error = TypesError;
|
||||
// TODO: should this live here?
|
||||
// attempts to replicate cosmos-sdk's coin metadata
|
||||
// https://docs.cosmos.network/master/architecture/adr-024-coin-metadata.html
|
||||
// this way we could more easily handle multiple coin types simultaneously (like nym/nyx/nymt/nyx + local currencies)
|
||||
#[derive(Debug)]
|
||||
pub struct DenomUnit {
|
||||
pub denom: Denom,
|
||||
pub exponent: u32,
|
||||
// pub aliases: Vec<String>,
|
||||
}
|
||||
|
||||
fn try_from(value: CosmosDenom) -> Result<Self, Self::Error> {
|
||||
CurrencyDenom::parse(value.as_ref())
|
||||
impl DenomUnit {
|
||||
pub fn new(denom: Denom, exponent: u32) -> Self {
|
||||
DenomUnit { denom, exponent }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/CurrencyStringMajorAmount.ts")
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct MajorAmountString(String); // see https://github.com/Aleph-Alpha/ts-rs/issues/51 for exporting type aliases
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/Currency.ts")
|
||||
)]
|
||||
// #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct MajorCurrencyAmount {
|
||||
// temporarly going back to original impl to speed up merge
|
||||
pub amount: MajorAmountString,
|
||||
pub denom: CurrencyDenom,
|
||||
// // temporary...
|
||||
// #[cfg_attr(feature = "generate-ts", ts(skip))]
|
||||
// pub coin: Coin,
|
||||
#[derive(Debug)]
|
||||
pub struct CoinMetadata {
|
||||
pub denom_units: Vec<DenomUnit>,
|
||||
pub base: Denom,
|
||||
pub display: Denom,
|
||||
}
|
||||
|
||||
// impl JsonSchema for MajorCurrencyAmount {
|
||||
// fn schema_name() -> String {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
impl CoinMetadata {
|
||||
pub fn new(denom_units: Vec<DenomUnit>, base: Denom, display: Denom) -> Self {
|
||||
CoinMetadata {
|
||||
denom_units,
|
||||
base,
|
||||
display,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_exponent(&self, denom: &str) -> Option<u32> {
|
||||
self.denom_units
|
||||
.iter()
|
||||
.find(|denom_unit| denom_unit.denom == denom)
|
||||
.map(|denom_unit| denom_unit.exponent)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DenomDetails> for CoinMetadata {
|
||||
fn from(denom_details: DenomDetails) -> Self {
|
||||
CoinMetadata::new(
|
||||
vec![
|
||||
DenomUnit::new(denom_details.base.into(), 0),
|
||||
DenomUnit::new(denom_details.display.into(), denom_details.display_exponent),
|
||||
],
|
||||
denom_details.base.into(),
|
||||
denom_details.display.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DenomDetailsOwned> for CoinMetadata {
|
||||
fn from(denom_details: DenomDetailsOwned) -> Self {
|
||||
CoinMetadata::new(
|
||||
vec![
|
||||
DenomUnit::new(denom_details.base.clone(), 0),
|
||||
DenomUnit::new(
|
||||
denom_details.display.clone(),
|
||||
denom_details.display_exponent,
|
||||
),
|
||||
],
|
||||
denom_details.base,
|
||||
denom_details.display,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// tries to semi-replicate cosmos-sdk's DecCoin for being able to handle tokens with decimal amounts
|
||||
// https://github.com/cosmos/cosmos-sdk/blob/v0.45.4/types/dec_coin.go
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)]
|
||||
pub struct DecCoin {
|
||||
//
|
||||
pub denom: Denom,
|
||||
// Decimal is already serialized to string and using string in its schema, so lets also go straight to string for ts_rs
|
||||
// todo: is `Decimal` the correct type to use? Do we want to depend on cosmwasm_std here?
|
||||
pub amount: Decimal,
|
||||
}
|
||||
|
||||
impl MajorCurrencyAmount {
|
||||
pub fn new(amount: &str, denom: CurrencyDenom) -> MajorCurrencyAmount {
|
||||
MajorCurrencyAmount {
|
||||
amount: MajorAmountString(amount.to_string()),
|
||||
denom,
|
||||
// I had to implement it manually to correctly set dependencies
|
||||
#[cfg(feature = "generate-ts")]
|
||||
impl TS for DecCoin {
|
||||
const EXPORT_TO: Option<&'static str> = Some("ts-packages/types/src/types/rust/DecCoin.ts");
|
||||
|
||||
fn decl() -> String {
|
||||
format!("type {} = {};", Self::name(), Self::inline())
|
||||
}
|
||||
|
||||
fn name() -> String {
|
||||
"DecCoin".into()
|
||||
}
|
||||
|
||||
fn inline() -> String {
|
||||
"{ denom: CurrencyDenom, amount: string }".into()
|
||||
}
|
||||
|
||||
fn dependencies() -> Vec<Dependency> {
|
||||
vec![Dependency::from_ty::<CurrencyDenom>()
|
||||
.expect("TS was incorrectly defined on `CurrencyDenom`")]
|
||||
}
|
||||
|
||||
fn transparent() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl DecCoin {
|
||||
pub fn new_base<S: Into<String>>(amount: impl Into<Uint128>, denom: S) -> Self {
|
||||
DecCoin {
|
||||
denom: denom.into(),
|
||||
amount: Decimal::from_atomics(amount, 0).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zero(denom: &CurrencyDenom) -> MajorCurrencyAmount {
|
||||
MajorCurrencyAmount::new("0", denom.clone())
|
||||
pub fn zero<S: Into<String>>(denom: S) -> Self {
|
||||
DecCoin {
|
||||
denom: denom.into(),
|
||||
amount: Decimal::zero(),
|
||||
}
|
||||
}
|
||||
//
|
||||
// pub fn from_cosmrs_coin(coin: &CosmosCoin) -> Result<MajorCurrencyAmount, TypesError> {
|
||||
// MajorCurrencyAmount::from_cosmrs_decimal_and_denom(coin.amount, coin.denom.to_string())
|
||||
// }
|
||||
//
|
||||
// pub fn from_minor_uint128_and_denom(
|
||||
// amount_minor: Uint128,
|
||||
// denom_minor: &str,
|
||||
// ) -> Result<MajorCurrencyAmount, TypesError> {
|
||||
// MajorCurrencyAmount::from_minor_decimal_and_denom(
|
||||
// Decimal::from_atomics(amount_minor, 0)?,
|
||||
// denom_minor,
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// pub fn from_minor_decimal_and_denom(
|
||||
// amount_minor: Decimal,
|
||||
// denom_minor: &str,
|
||||
// ) -> Result<MajorCurrencyAmount, TypesError> {
|
||||
// if !(denom_minor.starts_with('u') || denom_minor.starts_with('U')) {
|
||||
// return Err(TypesError::InvalidDenom(denom_minor.to_string()));
|
||||
// }
|
||||
// let major = amount_minor / Uint128::from(1_000_000u64);
|
||||
// if let Ok(denom) = CurrencyDenom::from_str(&denom_minor[1..].to_string()) {
|
||||
// return Ok(MajorCurrencyAmount {
|
||||
// amount: MajorAmountString(major.to_string()),
|
||||
// denom,
|
||||
// });
|
||||
// }
|
||||
// Err(TypesError::InvalidDenom(denom_minor.to_string()))
|
||||
// }
|
||||
// pub fn from_decimal_and_denom(
|
||||
// amount: Decimal,
|
||||
// denom: String,
|
||||
// ) -> Result<MajorCurrencyAmount, TypesError> {
|
||||
// if denom.starts_with('u') || denom.starts_with('U') {
|
||||
// return MajorCurrencyAmount::from_minor_decimal_and_denom(amount, &denom);
|
||||
// }
|
||||
// if let Ok(denom) = CurrencyDenom::from_str(denom.as_str()) {
|
||||
// return Ok(MajorCurrencyAmount {
|
||||
// amount: MajorAmountString(amount.to_string()),
|
||||
// denom,
|
||||
// });
|
||||
// }
|
||||
// Err(TypesError::InvalidDenom(denom))
|
||||
// }
|
||||
// pub fn from_cosmrs_decimal_and_denom(
|
||||
// amount: CosmosDecimal,
|
||||
// denom: String,
|
||||
// ) -> Result<MajorCurrencyAmount, TypesError> {
|
||||
// if denom.starts_with('u') || denom.starts_with('U') {
|
||||
// return match Decimal::from_str(&amount.to_string()) {
|
||||
// Ok(amount) => MajorCurrencyAmount::from_minor_decimal_and_denom(amount, &denom),
|
||||
// Err(_e) => Err(TypesError::InvalidAmount(amount.to_string())),
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// if let Ok(denom) = CurrencyDenom::from_str(denom.as_str()) {
|
||||
// return Ok(MajorCurrencyAmount {
|
||||
// amount: MajorAmountString(amount.to_string()),
|
||||
// denom,
|
||||
// });
|
||||
// }
|
||||
// Err(TypesError::InvalidDenom(denom))
|
||||
// }
|
||||
//
|
||||
// pub fn into_cosmos_coin(self) -> CosmosCoin {
|
||||
// self.coin.into()
|
||||
// }
|
||||
//
|
||||
// pub fn to_minor_uint128(&self) -> Result<Uint128, TypesError> {
|
||||
// if self.amount.0.contains('.') {
|
||||
// // has a decimal point (Cosmos assumes "." is the decimal separator)
|
||||
// let parts = self.amount.0.split('.');
|
||||
// let str = parts.collect_vec();
|
||||
// if str.is_empty() || str.len() > 2 {
|
||||
// return Err(TypesError::InvalidAmount("Amount is invalid".to_string()));
|
||||
// }
|
||||
// if str.len() == 2 {
|
||||
// // has a decimal, so check decimal places first
|
||||
// if str[1].len() > 6 {
|
||||
// return Err(TypesError::InvalidDenom(
|
||||
// "Amount is invalid, only 6 decimal places of precision are allowed"
|
||||
// .to_string(),
|
||||
// ));
|
||||
// }
|
||||
//
|
||||
// // so multiple whole part by 1e6 and add decimal part
|
||||
// let whole_part = Uint128::from_str(str[0])? * Uint128::from(1_000_000u64);
|
||||
//
|
||||
// // TODO: has Rust got anything that deals with fixed point values, or parsing from format strings? Leading zeroes are causing issues
|
||||
// return match format!("0.{}", str[1]).parse::<f64>() {
|
||||
// Ok(decimal_part_float) => {
|
||||
// // this makes an assumption that 6 decimal places of f64 can never lose precision
|
||||
// let truncated = (decimal_part_float * 1_000_000.).trunc() as u32;
|
||||
// let decimal_part = Uint128::from(truncated);
|
||||
// let sum = whole_part + decimal_part;
|
||||
// Ok(sum)
|
||||
// }
|
||||
// Err(_e) => Err(TypesError::InvalidAmount(
|
||||
// "Amount decimal part is invalid".to_string(),
|
||||
// )),
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let major = Uint128::from_str(&self.amount.0)?;
|
||||
// let scaled = major * Uint128::new(1_000_000u128);
|
||||
// Ok(scaled)
|
||||
// }
|
||||
|
||||
// pub fn denom_to_string(&self) -> String {
|
||||
// self.denom.to_string()
|
||||
// }
|
||||
pub fn new_scaled_up<S: Into<String>>(
|
||||
base_amount: impl Into<Uint128>,
|
||||
denom: S,
|
||||
exponent: u32,
|
||||
) -> Result<Self, TypesError> {
|
||||
let base_amount = Decimal::from_atomics(base_amount, 0).unwrap();
|
||||
Ok(DecCoin {
|
||||
denom: denom.into(),
|
||||
amount: try_scale_up_decimal(base_amount, exponent)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_scaled_down<S: Into<String>>(
|
||||
base_amount: impl Into<Uint128>,
|
||||
denom: S,
|
||||
exponent: u32,
|
||||
) -> Result<Self, TypesError> {
|
||||
let base_amount = Decimal::from_atomics(base_amount, 0).unwrap();
|
||||
Ok(DecCoin {
|
||||
denom: denom.into(),
|
||||
amount: try_scale_down_decimal(base_amount, exponent)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn try_scale_down_value(&self, exponent: u32) -> Result<Decimal, TypesError> {
|
||||
try_scale_down_decimal(self.amount, exponent)
|
||||
}
|
||||
|
||||
pub fn try_scale_up_value(&self, exponent: u32) -> Result<Decimal, TypesError> {
|
||||
try_scale_up_decimal(self.amount, exponent)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MajorCurrencyAmount {
|
||||
// TODO: should thoese live here?
|
||||
pub fn try_scale_down_decimal(dec: Decimal, exponent: u32) -> Result<Decimal, TypesError> {
|
||||
let rhs = 10u128
|
||||
.checked_pow(exponent)
|
||||
.ok_or(TypesError::UnsupportedExponent(exponent))?;
|
||||
let denominator = dec
|
||||
.denominator()
|
||||
.checked_mul(rhs.into())
|
||||
.map_err(|_| TypesError::UnsupportedExponent(exponent))?;
|
||||
|
||||
Ok(Decimal::from_ratio(dec.numerator(), denominator))
|
||||
}
|
||||
|
||||
pub fn try_scale_up_decimal(dec: Decimal, exponent: u32) -> Result<Decimal, TypesError> {
|
||||
let rhs = 10u128
|
||||
.checked_pow(exponent)
|
||||
.ok_or(TypesError::UnsupportedExponent(exponent))?;
|
||||
let denominator = dec
|
||||
.denominator()
|
||||
.checked_div(rhs.into())
|
||||
.map_err(|_| TypesError::UnsupportedExponent(exponent))?;
|
||||
|
||||
Ok(Decimal::from_ratio(dec.numerator(), denominator))
|
||||
}
|
||||
|
||||
pub fn try_convert_decimal_to_u128(dec: Decimal) -> Result<u128, TypesError> {
|
||||
let whole = dec.numerator() / dec.denominator();
|
||||
|
||||
// unwrap is fine as we're not dividing by zero here
|
||||
let fractional = (dec.numerator()).checked_rem(dec.denominator()).unwrap();
|
||||
|
||||
// we cannot convert as we'd lose our decimal places
|
||||
// (for example if somebody attempted to represent our gas price (WHICH YOU SHOULDN'T DO) as DecCoin)
|
||||
if fractional != Uint128::zero() {
|
||||
return Err(TypesError::LossyCoinConversion);
|
||||
}
|
||||
Ok(whole.u128())
|
||||
}
|
||||
|
||||
impl Display for DecCoin {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} {}", self.amount.0, self.denom)
|
||||
write!(f, "{} {}", self.amount, self.denom)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: cleanup after merge
|
||||
impl From<CosmosCoin> for MajorCurrencyAmount {
|
||||
fn from(c: CosmosCoin) -> Self {
|
||||
MajorCurrencyAmount::from(Coin::from(c))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CosmWasmCoin> for MajorCurrencyAmount {
|
||||
fn from(c: CosmWasmCoin) -> Self {
|
||||
MajorCurrencyAmount::from(Coin::from(c))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Coin> for MajorCurrencyAmount {
|
||||
impl From<Coin> for DecCoin {
|
||||
fn from(coin: Coin) -> Self {
|
||||
// current assumption: MajorCurrencyAmount is represented as decimal with 6 decimal points
|
||||
// unwrap is fine as we haven't exceeded decimal range since our coins are at max 1B in value
|
||||
// (this is a weak assumption, but for solving this merge conflict it's good enough temporary workaround)
|
||||
let amount = Decimal::from_atomics(coin.amount, 6).unwrap();
|
||||
MajorCurrencyAmount {
|
||||
amount: MajorAmountString(amount.to_string()),
|
||||
denom: CurrencyDenom::parse(&coin.denom).expect("this will go away after the merge..."),
|
||||
}
|
||||
DecCoin::new_base(coin.amount, coin.denom)
|
||||
}
|
||||
}
|
||||
|
||||
// temporary...
|
||||
impl From<MajorCurrencyAmount> for CosmosCoin {
|
||||
fn from(c: MajorCurrencyAmount) -> CosmosCoin {
|
||||
let c: Coin = c.into();
|
||||
c.into()
|
||||
}
|
||||
}
|
||||
// this conversion assumes same denomination
|
||||
impl TryFrom<DecCoin> for Coin {
|
||||
type Error = TypesError;
|
||||
|
||||
impl From<MajorCurrencyAmount> for CosmWasmCoin {
|
||||
fn from(c: MajorCurrencyAmount) -> CosmWasmCoin {
|
||||
let c: Coin = c.into();
|
||||
c.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MajorCurrencyAmount> for Coin {
|
||||
fn from(c: MajorCurrencyAmount) -> Coin {
|
||||
let decimal: Decimal = c
|
||||
.amount
|
||||
.0
|
||||
.parse()
|
||||
.expect("stringified amount should have been a valid decimal");
|
||||
|
||||
// again, temporary
|
||||
let exp = Uint128::new(1000000);
|
||||
let val = decimal.mul(exp);
|
||||
|
||||
// again, terrible assumption for denom, but it works temporarily...
|
||||
Coin {
|
||||
amount: val.u128(),
|
||||
denom: format!("u{}", c.denom).to_lowercase(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for MajorCurrencyAmount {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
// again, temporary workaround to help with merge
|
||||
(Coin::from(self).try_add(&Coin::from(rhs)))
|
||||
.expect("provided coins had different denoms")
|
||||
.into()
|
||||
fn try_from(value: DecCoin) -> Result<Self, Self::Error> {
|
||||
Ok(Coin {
|
||||
amount: try_convert_decimal_to_u128(value.try_scale_down_value(0)?)?,
|
||||
denom: value.denom,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use cosmrs::Coin as CosmosCoin;
|
||||
use cosmrs::Decimal as CosmosDecimal;
|
||||
use cosmrs::Denom as CosmosDenom;
|
||||
use cosmwasm_std::Coin as CosmWasmCoin;
|
||||
use cosmwasm_std::Decimal as CosmWasmDecimal;
|
||||
use serde_json::json;
|
||||
use std::str::FromStr;
|
||||
use std::convert::TryFrom;
|
||||
use std::string::ToString;
|
||||
|
||||
#[test]
|
||||
fn json_to_major_currency_amount() {
|
||||
let nym = json!({
|
||||
"amount": "1",
|
||||
"denom": "NYM"
|
||||
});
|
||||
let nymt = json!({
|
||||
"amount": "1",
|
||||
"denom": "NYMT"
|
||||
});
|
||||
fn dec_value_scale_down() {
|
||||
let dec = DecCoin {
|
||||
denom: "foo".to_string(),
|
||||
amount: "1234007000".parse().unwrap(),
|
||||
};
|
||||
|
||||
let test_nym_amount = MajorCurrencyAmount::new("1", CurrencyDenom::Nym);
|
||||
let test_nymt_amount = MajorCurrencyAmount::new("1", CurrencyDenom::Nymt);
|
||||
|
||||
let nym_amount = serde_json::from_value::<MajorCurrencyAmount>(nym).unwrap();
|
||||
let nymt_amount = serde_json::from_value::<MajorCurrencyAmount>(nymt).unwrap();
|
||||
|
||||
assert_eq!(nym_amount, test_nym_amount);
|
||||
assert_eq!(nymt_amount, test_nymt_amount);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minor_amount_json_to_major_currency_amount() {
|
||||
let one_micro_nym = json!({
|
||||
"amount": "0.000001",
|
||||
"denom": "NYM"
|
||||
});
|
||||
|
||||
let expected_nym_amount = MajorCurrencyAmount::new("0.000001", CurrencyDenom::Nym);
|
||||
let actual_nym_amount =
|
||||
serde_json::from_value::<MajorCurrencyAmount>(one_micro_nym).unwrap();
|
||||
|
||||
assert_eq!(expected_nym_amount, actual_nym_amount);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn denom_from_str() {
|
||||
assert_eq!(CurrencyDenom::from_str("nym").unwrap(), CurrencyDenom::Nym);
|
||||
assert_eq!(
|
||||
CurrencyDenom::from_str("nymt").unwrap(),
|
||||
CurrencyDenom::Nymt
|
||||
);
|
||||
assert_eq!(CurrencyDenom::from_str("NYM").unwrap(), CurrencyDenom::Nym);
|
||||
assert_eq!(
|
||||
CurrencyDenom::from_str("NYMT").unwrap(),
|
||||
CurrencyDenom::Nymt
|
||||
);
|
||||
assert_eq!(CurrencyDenom::from_str("NyM").unwrap(), CurrencyDenom::Nym);
|
||||
assert_eq!(
|
||||
CurrencyDenom::from_str("NYmt").unwrap(),
|
||||
CurrencyDenom::Nymt
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
CurrencyDenom::from_str("foo").unwrap_err(),
|
||||
strum::ParseError::VariantNotFound,
|
||||
));
|
||||
|
||||
// denominations must all be major
|
||||
assert!(matches!(
|
||||
CurrencyDenom::from_str("unym").unwrap_err(),
|
||||
strum::ParseError::VariantNotFound,
|
||||
));
|
||||
assert!(matches!(
|
||||
CurrencyDenom::from_str("unymt").unwrap_err(),
|
||||
strum::ParseError::VariantNotFound,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string() {
|
||||
assert_eq!(
|
||||
MajorCurrencyAmount::new("1", CurrencyDenom::Nym).to_string(),
|
||||
"1 NYM"
|
||||
"1234007000".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_down_value(0).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
MajorCurrencyAmount::new("1", CurrencyDenom::Nymt).to_string(),
|
||||
"1 NYMT"
|
||||
"123400700".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_down_value(1).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
MajorCurrencyAmount::new("1000000000000", CurrencyDenom::Nym).to_string(),
|
||||
"1000000000000 NYM"
|
||||
"12340070".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_down_value(2).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"123400.7".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_down_value(4).unwrap()
|
||||
);
|
||||
|
||||
let dec = DecCoin {
|
||||
denom: "foo".to_string(),
|
||||
amount: "10000000000".parse().unwrap(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
"100".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_down_value(8).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"1".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_down_value(10).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"0.01".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_down_value(12).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minor_coin_to_major_currency() {
|
||||
let cosmos_coin = CosmosCoin {
|
||||
amount: CosmosDecimal::from(1u64),
|
||||
denom: CosmosDenom::from_str("unym").unwrap(),
|
||||
fn dec_value_scale_up() {
|
||||
let dec = DecCoin {
|
||||
denom: "foo".to_string(),
|
||||
amount: "1234.56".parse().unwrap(),
|
||||
};
|
||||
let c = MajorCurrencyAmount::from(cosmos_coin);
|
||||
assert_eq!(c, MajorCurrencyAmount::new("0.000001", CurrencyDenom::Nym));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minor_cosmwasm_coin_to_major_currency() {
|
||||
let coin = CosmWasmCoin {
|
||||
amount: Uint128::from(1u64),
|
||||
denom: "unym".to_string(),
|
||||
};
|
||||
println!(
|
||||
"from_atomics = {}",
|
||||
CosmWasmDecimal::from_atomics(coin.amount, 6).unwrap()
|
||||
assert_eq!(
|
||||
"1234.56".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(0).unwrap()
|
||||
);
|
||||
let c: MajorCurrencyAmount = coin.into();
|
||||
assert_eq!(c, MajorCurrencyAmount::new("0.000001", CurrencyDenom::Nym));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minor_cosmwasm_coin_to_major_currency_2() {
|
||||
let coin = CosmWasmCoin {
|
||||
amount: Uint128::from(1_000_000u64),
|
||||
denom: "unym".to_string(),
|
||||
};
|
||||
println!(
|
||||
"from_atomics = {:?}",
|
||||
CosmWasmDecimal::from_atomics(coin.amount, 6)
|
||||
.unwrap()
|
||||
.to_string()
|
||||
assert_eq!(
|
||||
"12345.6".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(1).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"123456".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(2).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"1234560".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(3).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"12345600".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(4).unwrap()
|
||||
);
|
||||
let c: MajorCurrencyAmount = coin.into();
|
||||
assert_eq!(c, MajorCurrencyAmount::new("1", CurrencyDenom::Nym));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn major_currency_to_minor_cosmos_coin() {
|
||||
let expected_cosmos_coin = CosmosCoin {
|
||||
amount: CosmosDecimal::from(1u64),
|
||||
denom: CosmosDenom::from_str("unym").unwrap(),
|
||||
let dec = DecCoin {
|
||||
denom: "foo".to_string(),
|
||||
amount: "0.00000123".parse().unwrap(),
|
||||
};
|
||||
let c = MajorCurrencyAmount::new("0.000001", CurrencyDenom::Nym);
|
||||
let minor_cosmos_coin = c.into();
|
||||
assert_eq!(expected_cosmos_coin, minor_cosmos_coin);
|
||||
assert_eq!("unym", minor_cosmos_coin.denom.to_string());
|
||||
|
||||
assert_eq!(
|
||||
"0.0000123".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(1).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"0.000123".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(2).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"123".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(8).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"1230".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(9).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"12300".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(10).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn major_currency_to_minor_cosmos_coin_2() {
|
||||
let expected_cosmos_coin = CosmosCoin {
|
||||
amount: CosmosDecimal::from(1000000u64),
|
||||
denom: CosmosDenom::from_str("unym").unwrap(),
|
||||
fn coin_to_dec_coin() {
|
||||
let coin = Coin::new(123, "foo");
|
||||
let dec = DecCoin::from(coin.clone());
|
||||
assert_eq!(coin.denom, dec.denom);
|
||||
assert_eq!(dec.amount, Decimal::from_atomics(coin.amount, 0).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dec_coin_to_coin() {
|
||||
let dec = DecCoin {
|
||||
denom: "foo".to_string(),
|
||||
amount: "123".parse().unwrap(),
|
||||
};
|
||||
let c = MajorCurrencyAmount::new("1", CurrencyDenom::Nym);
|
||||
let minor_cosmos_coin = c.into();
|
||||
assert_eq!(expected_cosmos_coin, minor_cosmos_coin);
|
||||
assert_eq!("unym", minor_cosmos_coin.denom.to_string());
|
||||
let coin = Coin::try_from(dec.clone()).unwrap();
|
||||
assert_eq!(dec.denom, coin.denom);
|
||||
assert_eq!(coin.amount, 123u128);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minor_cosmos_coin_to_major_currency_string() {
|
||||
// check minor cosmos coin is converted to major value
|
||||
let cosmos_coin = CosmosCoin {
|
||||
amount: CosmosDecimal::from(1u64),
|
||||
denom: CosmosDenom::from_str("unym").unwrap(),
|
||||
};
|
||||
let c = MajorCurrencyAmount::from(cosmos_coin);
|
||||
assert_eq!(c.to_string(), "0.000001 NYM");
|
||||
fn converting_to_display() {
|
||||
let reg = RegisteredCoins::default_denoms(Network::MAINNET);
|
||||
let values = vec![
|
||||
(1u128, "0.000001"),
|
||||
(10u128, "0.00001"),
|
||||
(100u128, "0.0001"),
|
||||
(1000u128, "0.001"),
|
||||
(10000u128, "0.01"),
|
||||
(100000u128, "0.1"),
|
||||
(1000000u128, "1"),
|
||||
(1234567u128, "1.234567"),
|
||||
(123456700u128, "123.4567"),
|
||||
];
|
||||
|
||||
for (raw, expected) in values {
|
||||
let coin = Coin::new(raw, Network::MAINNET.mix_denom().base);
|
||||
let display = reg.attempt_convert_to_display_dec_coin(coin).unwrap();
|
||||
assert_eq!(Network::MAINNET.mix_denom().display, display.denom);
|
||||
assert_eq!(expected, display.amount.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn denom_to_string() {
|
||||
let c = MajorCurrencyAmount::new("1", CurrencyDenom::Nym);
|
||||
let denom = c.denom.to_string();
|
||||
assert_eq!(denom, "NYM".to_string());
|
||||
fn converting_to_base() {
|
||||
let reg = RegisteredCoins::default_denoms(Network::MAINNET);
|
||||
let values = vec![
|
||||
(1u128, "0.000001"),
|
||||
(10u128, "0.00001"),
|
||||
(100u128, "0.0001"),
|
||||
(1000u128, "0.001"),
|
||||
(10000u128, "0.01"),
|
||||
(100000u128, "0.1"),
|
||||
(1000000u128, "1"),
|
||||
(1234567u128, "1.234567"),
|
||||
(123456700u128, "123.4567"),
|
||||
];
|
||||
|
||||
for (expected, raw_display) in values {
|
||||
let coin = DecCoin {
|
||||
denom: Network::MAINNET.mix_denom().display.into(),
|
||||
amount: raw_display.parse().unwrap(),
|
||||
};
|
||||
let base = reg.attempt_convert_to_base_coin(coin).unwrap();
|
||||
assert_eq!(Network::MAINNET.mix_denom().base, base.denom);
|
||||
assert_eq!(expected, base.amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use log::error;
|
||||
use crate::currency::{DecCoin, RegisteredCoins};
|
||||
use crate::error::TypesError;
|
||||
use mixnet_contract_common::mixnode::DelegationEvent as ContractDelegationEvent;
|
||||
use mixnet_contract_common::mixnode::PendingUndelegate as ContractPendingUndelegate;
|
||||
use mixnet_contract_common::Delegation as MixnetContractDelegation;
|
||||
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::error::TypesError;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
@@ -18,31 +15,22 @@ use crate::error::TypesError;
|
||||
pub struct Delegation {
|
||||
pub owner: String,
|
||||
pub node_identity: String,
|
||||
pub amount: MajorCurrencyAmount,
|
||||
pub amount: DecCoin,
|
||||
pub block_height: u64,
|
||||
pub proxy: Option<String>, // proxy address used to delegate the funds on behalf of another address
|
||||
}
|
||||
|
||||
impl TryFrom<MixnetContractDelegation> for Delegation {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(value: MixnetContractDelegation) -> Result<Self, Self::Error> {
|
||||
let MixnetContractDelegation {
|
||||
owner,
|
||||
node_identity,
|
||||
amount,
|
||||
block_height,
|
||||
proxy,
|
||||
} = value;
|
||||
|
||||
let amount: MajorCurrencyAmount = amount.into();
|
||||
|
||||
impl Delegation {
|
||||
pub fn from_mixnet_contract(
|
||||
delegation: MixnetContractDelegation,
|
||||
reg: &RegisteredCoins,
|
||||
) -> Result<Self, TypesError> {
|
||||
Ok(Delegation {
|
||||
owner: owner.into_string(),
|
||||
node_identity,
|
||||
amount,
|
||||
block_height,
|
||||
proxy: proxy.map(|p| p.into_string()),
|
||||
owner: delegation.owner.to_string(),
|
||||
node_identity: delegation.node_identity,
|
||||
amount: reg.attempt_convert_to_display_dec_coin(delegation.amount.into())?,
|
||||
block_height: delegation.block_height,
|
||||
proxy: delegation.proxy.map(|d| d.to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -54,7 +42,7 @@ impl TryFrom<MixnetContractDelegation> for Delegation {
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
|
||||
pub struct DelegationRecord {
|
||||
pub amount: MajorCurrencyAmount,
|
||||
pub amount: DecCoin,
|
||||
pub block_height: u64,
|
||||
pub delegated_on_iso_datetime: String,
|
||||
pub uses_vesting_contract_tokens: bool,
|
||||
@@ -69,16 +57,16 @@ pub struct DelegationRecord {
|
||||
pub struct DelegationWithEverything {
|
||||
pub owner: String,
|
||||
pub node_identity: String,
|
||||
pub amount: MajorCurrencyAmount,
|
||||
pub total_delegation: Option<MajorCurrencyAmount>,
|
||||
pub pledge_amount: Option<MajorCurrencyAmount>,
|
||||
pub amount: DecCoin,
|
||||
pub total_delegation: Option<DecCoin>,
|
||||
pub pledge_amount: Option<DecCoin>,
|
||||
pub block_height: u64,
|
||||
pub delegated_on_iso_datetime: String,
|
||||
pub profit_margin_percent: Option<u8>,
|
||||
pub avg_uptime_percent: Option<u8>,
|
||||
pub stake_saturation: Option<f32>,
|
||||
pub uses_vesting_contract_tokens: bool,
|
||||
pub accumulated_rewards: Option<MajorCurrencyAmount>,
|
||||
pub accumulated_rewards: Option<DecCoin>,
|
||||
pub pending_events: Vec<DelegationEvent>,
|
||||
pub history: Vec<DelegationRecord>,
|
||||
}
|
||||
@@ -92,34 +80,7 @@ pub struct DelegationWithEverything {
|
||||
pub struct DelegationResult {
|
||||
source_address: String,
|
||||
target_address: String,
|
||||
amount: Option<MajorCurrencyAmount>,
|
||||
}
|
||||
|
||||
impl DelegationResult {
|
||||
pub fn new(
|
||||
source_address: &str,
|
||||
target_address: &str,
|
||||
amount: Option<MajorCurrencyAmount>,
|
||||
) -> DelegationResult {
|
||||
DelegationResult {
|
||||
source_address: source_address.to_string(),
|
||||
target_address: target_address.to_string(),
|
||||
amount,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MixnetContractDelegation> for DelegationResult {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(delegation: MixnetContractDelegation) -> Result<Self, Self::Error> {
|
||||
let amount: MajorCurrencyAmount = delegation.amount.clone().into();
|
||||
Ok(DelegationResult {
|
||||
source_address: delegation.owner().to_string(),
|
||||
target_address: delegation.node_identity(),
|
||||
amount: Some(amount),
|
||||
})
|
||||
}
|
||||
amount: Option<DecCoin>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
@@ -143,36 +104,34 @@ pub struct DelegationEvent {
|
||||
pub kind: DelegationEventKind,
|
||||
pub node_identity: String,
|
||||
pub address: String,
|
||||
pub amount: Option<MajorCurrencyAmount>,
|
||||
pub amount: Option<DecCoin>,
|
||||
pub block_height: u64,
|
||||
pub proxy: Option<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<ContractDelegationEvent> for DelegationEvent {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(event: ContractDelegationEvent) -> Result<Self, Self::Error> {
|
||||
match event {
|
||||
ContractDelegationEvent::Delegate(delegation) => {
|
||||
let amount: MajorCurrencyAmount = delegation.amount.into();
|
||||
Ok(DelegationEvent {
|
||||
kind: DelegationEventKind::Delegate,
|
||||
block_height: delegation.block_height,
|
||||
address: delegation.owner.into_string(),
|
||||
node_identity: delegation.node_identity,
|
||||
amount: Some(amount),
|
||||
proxy: delegation.proxy.map(|p| p.into_string()),
|
||||
})
|
||||
}
|
||||
ContractDelegationEvent::Undelegate(pending_undelegate) => Ok(DelegationEvent {
|
||||
impl DelegationEvent {
|
||||
pub fn from_mixnet_contract(
|
||||
event: ContractDelegationEvent,
|
||||
reg: &RegisteredCoins,
|
||||
) -> Result<Self, TypesError> {
|
||||
Ok(match event {
|
||||
ContractDelegationEvent::Delegate(delegation) => DelegationEvent {
|
||||
kind: DelegationEventKind::Delegate,
|
||||
block_height: delegation.block_height,
|
||||
address: delegation.owner.into_string(),
|
||||
node_identity: delegation.node_identity,
|
||||
amount: Some(reg.attempt_convert_to_display_dec_coin(delegation.amount.into())?),
|
||||
proxy: delegation.proxy.map(|p| p.into_string()),
|
||||
},
|
||||
ContractDelegationEvent::Undelegate(pending_undelegate) => DelegationEvent {
|
||||
kind: DelegationEventKind::Undelegate,
|
||||
block_height: pending_undelegate.block_height(),
|
||||
address: pending_undelegate.delegate().into_string(),
|
||||
node_identity: pending_undelegate.mix_identity(),
|
||||
amount: None,
|
||||
proxy: pending_undelegate.proxy().map(|p| p.into_string()),
|
||||
}),
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,30 +159,6 @@ impl From<ContractPendingUndelegate> for PendingUndelegate {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_contract_delegation_events(
|
||||
events: Vec<ContractDelegationEvent>,
|
||||
) -> Result<Vec<DelegationEvent>, TypesError> {
|
||||
let (events, errors): (Vec<_>, Vec<_>) = events
|
||||
.into_iter()
|
||||
.map(|delegation_event| delegation_event.try_into())
|
||||
.partition(Result::is_ok);
|
||||
|
||||
if errors.is_empty() {
|
||||
let events = events
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.collect::<Vec<DelegationEvent>>();
|
||||
return Ok(events);
|
||||
}
|
||||
let errors = errors
|
||||
.into_iter()
|
||||
.filter_map(|e| e.err())
|
||||
.collect::<Vec<TypesError>>();
|
||||
|
||||
error!("Failed to convert delegations: {:?}", errors);
|
||||
Err(TypesError::DelegationsInvalid)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
@@ -232,6 +167,6 @@ pub fn from_contract_delegation_events(
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct DelegationsSummaryResponse {
|
||||
pub delegations: Vec<DelegationWithEverything>,
|
||||
pub total_delegations: MajorCurrencyAmount,
|
||||
pub total_rewards: MajorCurrencyAmount,
|
||||
pub total_delegations: DecCoin,
|
||||
pub total_rewards: DecCoin,
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use thiserror::Error;
|
||||
use validator_client::validator_api::error::ValidatorAPIError;
|
||||
use validator_client::{nymd::error::NymdError, ValidatorClientError};
|
||||
|
||||
// TODO: ask @MS why this even exists
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TypesError {
|
||||
#[error("{source}")]
|
||||
@@ -63,6 +64,12 @@ pub enum TypesError {
|
||||
InvalidGatewayBond(),
|
||||
#[error("Invalid delegations")]
|
||||
DelegationsInvalid,
|
||||
#[error("Attempted to use too huge currency exponent ({0})")]
|
||||
UnsupportedExponent(u32),
|
||||
#[error("Attempted to convert coin that would have resulted in loss of precision")]
|
||||
LossyCoinConversion,
|
||||
#[error("The provided coin has an unknown denomination - {0}")]
|
||||
UnknownCoinDenom(String),
|
||||
}
|
||||
|
||||
impl Serialize for TypesError {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::currency::DecCoin;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator_client::nymd::Fee;
|
||||
|
||||
@@ -8,10 +8,16 @@ use ts_rs::{Dependency, TS};
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FeeDetails {
|
||||
// expected to be used by the wallet in order to display detailed fee information to the user
|
||||
pub amount: Option<MajorCurrencyAmount>,
|
||||
pub amount: Option<DecCoin>,
|
||||
pub fee: Fee,
|
||||
}
|
||||
|
||||
impl FeeDetails {
|
||||
pub fn new(amount: Option<DecCoin>, fee: Fee) -> Self {
|
||||
FeeDetails { amount, fee }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "generate-ts")]
|
||||
impl TS for FeeDetails {
|
||||
const EXPORT_TO: Option<&'static str> = Some("ts-packages/types/src/types/rust/FeeDetails.ts");
|
||||
@@ -25,13 +31,12 @@ impl TS for FeeDetails {
|
||||
}
|
||||
|
||||
fn inline() -> String {
|
||||
"{ amount: MajorCurrencyAmount | null, fee: Fee }".into()
|
||||
"{ amount: DecCoin | null, fee: Fee }".into()
|
||||
}
|
||||
|
||||
fn dependencies() -> Vec<Dependency> {
|
||||
vec![
|
||||
Dependency::from_ty::<MajorCurrencyAmount>()
|
||||
.expect("TS was incorrectly defined on `CurrencyDenom`"),
|
||||
Dependency::from_ty::<DecCoin>().expect("TS was incorrectly defined on `DecCoin`"),
|
||||
Dependency::from_ty::<ts_type_helpers::Fee>()
|
||||
.expect("TS was incorrectly defined on `ts_type_helpers::Fee`"),
|
||||
]
|
||||
|
||||
@@ -1,48 +1,29 @@
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::error::TypesError;
|
||||
use cosmrs::tx::Gas as CosmrsGas;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator_client::nymd::cosmwasm_client::types::GasInfo as ValidatorClientGasInfo;
|
||||
use validator_client::nymd::GasPrice;
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/Gas.ts")
|
||||
)]
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug)]
|
||||
pub struct Gas {
|
||||
/// units of gas used
|
||||
pub gas_units: u64,
|
||||
//
|
||||
// /// gas units converted to fee as major coin amount
|
||||
// pub amount: MajorCurrencyAmount,
|
||||
}
|
||||
|
||||
impl Gas {
|
||||
pub fn from_cosmrs_gas(value: CosmrsGas, _denom_minor: &str) -> Result<Gas, TypesError> {
|
||||
Ok(Gas {
|
||||
gas_units: value.value(),
|
||||
})
|
||||
|
||||
// // TODO: use simulator struct to do conversion to fee
|
||||
// let value_u128 = Uint128::from(value.value());
|
||||
// let amount = Decimal::new(value_u128) * Decimal::from_str("0.0025")?;
|
||||
// Ok(Gas {
|
||||
// gas_units: value.value(),
|
||||
// amount: MajorCurrencyAmount::from_minor_decimal_and_denom(amount, denom_minor)?,
|
||||
// })
|
||||
pub fn from_u64(value: u64) -> Gas {
|
||||
Gas { gas_units: value }
|
||||
}
|
||||
pub fn from_u64(value: u64, _denom_minor: &str) -> Result<Gas, TypesError> {
|
||||
Ok(Gas { gas_units: value })
|
||||
// todo!()
|
||||
// // TODO: use simulator struct to do conversion to fee
|
||||
// let value_u128 = Uint128::from(value);
|
||||
// let amount = Decimal::new(value_u128) * Decimal::from_str("0.0025")?;
|
||||
// Ok(Gas {
|
||||
// gas_units: value,
|
||||
// amount: MajorCurrencyAmount::from_minor_decimal_and_denom(amount, denom_minor)?,
|
||||
// })
|
||||
}
|
||||
|
||||
impl From<CosmrsGas> for Gas {
|
||||
fn from(gas: CosmrsGas) -> Self {
|
||||
Gas {
|
||||
gas_units: gas.value(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,44 +32,29 @@ impl Gas {
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/GasInfo.ts")
|
||||
)]
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug)]
|
||||
pub struct GasInfo {
|
||||
/// GasWanted is the maximum units of work we allow this tx to perform.
|
||||
pub gas_wanted: u64,
|
||||
pub gas_wanted: Gas,
|
||||
|
||||
/// GasUsed is the amount of gas actually consumed.
|
||||
pub gas_used: u64,
|
||||
pub gas_used: Gas,
|
||||
}
|
||||
|
||||
/// gas units converted to fee as major coin amount
|
||||
pub fee: MajorCurrencyAmount,
|
||||
impl From<ValidatorClientGasInfo> for GasInfo {
|
||||
fn from(info: ValidatorClientGasInfo) -> Self {
|
||||
GasInfo {
|
||||
gas_wanted: info.gas_wanted.into(),
|
||||
gas_used: info.gas_used.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GasInfo {
|
||||
pub fn from_validator_client_gas_info(
|
||||
value: ValidatorClientGasInfo,
|
||||
denom_minor: &str,
|
||||
) -> Result<GasInfo, TypesError> {
|
||||
// terrible workaround, but I don't want to break the current flow (just yet)
|
||||
let gas_price = GasPrice::new_with_default_price(denom_minor)?;
|
||||
let fee = (&gas_price) * value.gas_used;
|
||||
Ok(GasInfo {
|
||||
gas_wanted: value.gas_wanted.value(),
|
||||
gas_used: value.gas_used.value(),
|
||||
fee: fee.into(),
|
||||
})
|
||||
}
|
||||
pub fn from_u64(
|
||||
gas_wanted: u64,
|
||||
gas_used: u64,
|
||||
denom_minor: &str,
|
||||
) -> Result<GasInfo, TypesError> {
|
||||
// terrible workaround, but I don't want to break the current flow (just yet)
|
||||
let gas_price = GasPrice::new_with_default_price(denom_minor)?;
|
||||
let fee = (&gas_price) * CosmrsGas::from(gas_used);
|
||||
Ok(GasInfo {
|
||||
gas_wanted,
|
||||
gas_used,
|
||||
fee: fee.into(),
|
||||
})
|
||||
pub fn from_u64(gas_wanted: u64, gas_used: u64) -> GasInfo {
|
||||
GasInfo {
|
||||
gas_wanted: Gas::from_u64(gas_wanted),
|
||||
gas_used: Gas::from_u64(gas_used),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::currency::{DecCoin, RegisteredCoins};
|
||||
use crate::error::TypesError;
|
||||
use mixnet_contract_common::{
|
||||
Gateway as MixnetContractGateway, GatewayBond as MixnetContractGatewayBond,
|
||||
@@ -54,7 +54,7 @@ impl From<MixnetContractGateway> for Gateway {
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct GatewayBond {
|
||||
pub pledge_amount: MajorCurrencyAmount,
|
||||
pub pledge_amount: DecCoin,
|
||||
pub owner: String,
|
||||
pub block_height: u64,
|
||||
pub gateway: Gateway,
|
||||
@@ -63,38 +63,15 @@ pub struct GatewayBond {
|
||||
|
||||
impl GatewayBond {
|
||||
pub fn from_mixnet_contract_gateway_bond(
|
||||
bond: Option<MixnetContractGatewayBond>,
|
||||
) -> Result<Option<GatewayBond>, TypesError> {
|
||||
match bond {
|
||||
Some(bond) => {
|
||||
let bond: GatewayBond = bond.try_into()?;
|
||||
Ok(Some(bond))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MixnetContractGatewayBond> for GatewayBond {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(value: MixnetContractGatewayBond) -> Result<Self, Self::Error> {
|
||||
let MixnetContractGatewayBond {
|
||||
pledge_amount,
|
||||
owner,
|
||||
block_height,
|
||||
gateway,
|
||||
proxy,
|
||||
} = value;
|
||||
|
||||
let pledge_amount: MajorCurrencyAmount = pledge_amount.into();
|
||||
|
||||
bond: MixnetContractGatewayBond,
|
||||
reg: &RegisteredCoins,
|
||||
) -> Result<GatewayBond, TypesError> {
|
||||
Ok(GatewayBond {
|
||||
pledge_amount,
|
||||
owner: owner.into_string(),
|
||||
block_height,
|
||||
gateway: gateway.into(),
|
||||
proxy: proxy.map(|p| p.into_string()),
|
||||
pledge_amount: reg.attempt_convert_to_display_dec_coin(bond.pledge_amount.into())?,
|
||||
owner: bond.owner.to_string(),
|
||||
block_height: bond.block_height,
|
||||
gateway: bond.gateway.into(),
|
||||
proxy: bond.proxy.map(|p| p.into_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::currency::{DecCoin, RegisteredCoins};
|
||||
use crate::error::TypesError;
|
||||
use mixnet_contract_common::{
|
||||
Coin as CosmWasmCoin, MixNode as MixnetContractMixNode,
|
||||
MixNodeBond as MixnetContractMixNodeBond,
|
||||
MixNode as MixnetContractMixNode, MixNodeBond as MixnetContractMixNodeBond,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator_client::nymd::Coin;
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
@@ -58,67 +58,39 @@ impl From<MixnetContractMixNode> for MixNode {
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct MixNodeBond {
|
||||
pub pledge_amount: MajorCurrencyAmount,
|
||||
pub total_delegation: MajorCurrencyAmount,
|
||||
pub pledge_amount: DecCoin,
|
||||
pub total_delegation: DecCoin,
|
||||
pub owner: String,
|
||||
pub layer: String,
|
||||
pub block_height: u64,
|
||||
pub mix_node: MixNode,
|
||||
pub proxy: Option<String>,
|
||||
pub accumulated_rewards: Option<MajorCurrencyAmount>,
|
||||
pub accumulated_rewards: Option<DecCoin>,
|
||||
}
|
||||
|
||||
impl MixNodeBond {
|
||||
pub fn from_mixnet_contract_mixnode_bond(
|
||||
bond: Option<MixnetContractMixNodeBond>,
|
||||
) -> Result<Option<MixNodeBond>, TypesError> {
|
||||
match bond {
|
||||
Some(bond) => {
|
||||
let bond: MixNodeBond = bond.try_into()?;
|
||||
Ok(Some(bond))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MixnetContractMixNodeBond> for MixNodeBond {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(value: MixnetContractMixNodeBond) -> Result<Self, Self::Error> {
|
||||
let MixnetContractMixNodeBond {
|
||||
pledge_amount,
|
||||
total_delegation,
|
||||
owner,
|
||||
layer,
|
||||
block_height,
|
||||
mix_node,
|
||||
proxy,
|
||||
accumulated_rewards,
|
||||
} = value;
|
||||
|
||||
if pledge_amount.denom != total_delegation.denom {
|
||||
return Err(TypesError::InvalidDenom(
|
||||
"The pledge and delegation denominations do not match".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let denom = total_delegation.denom.clone();
|
||||
|
||||
let pledge_amount: MajorCurrencyAmount = pledge_amount.into();
|
||||
let total_delegation: MajorCurrencyAmount = total_delegation.into();
|
||||
let accumulated_rewards: Option<MajorCurrencyAmount> =
|
||||
accumulated_rewards.map(|r| CosmWasmCoin::new(r.u128(), denom).into());
|
||||
|
||||
bond: MixnetContractMixNodeBond,
|
||||
reg: &RegisteredCoins,
|
||||
) -> Result<MixNodeBond, TypesError> {
|
||||
let denom = bond.pledge_amount.denom.clone();
|
||||
Ok(MixNodeBond {
|
||||
pledge_amount,
|
||||
total_delegation,
|
||||
owner: owner.into_string(),
|
||||
layer: layer.into(),
|
||||
block_height,
|
||||
mix_node: mix_node.into(),
|
||||
proxy: proxy.map(|p| p.into_string()),
|
||||
accumulated_rewards,
|
||||
pledge_amount: reg.attempt_convert_to_display_dec_coin(bond.pledge_amount.into())?,
|
||||
total_delegation: reg
|
||||
.attempt_convert_to_display_dec_coin(bond.total_delegation.into())?,
|
||||
owner: bond.owner.into_string(),
|
||||
layer: bond.layer.into(),
|
||||
block_height: bond.block_height,
|
||||
mix_node: bond.mix_node.into(),
|
||||
proxy: bond.proxy.map(|p| p.to_string()),
|
||||
accumulated_rewards: bond
|
||||
.accumulated_rewards
|
||||
.map(|reward| {
|
||||
// here we're making an assumption that rewards always use the same denom as the pledge
|
||||
// (which I think is a reasonable assumption)
|
||||
reg.attempt_convert_to_display_dec_coin(Coin::new(reward.u128(), denom))
|
||||
})
|
||||
.transpose()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::currency::DecCoin;
|
||||
use crate::error::TypesError;
|
||||
use crate::gas::GasInfo;
|
||||
use crate::gas::{Gas, GasInfo};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator_client::nymd::cosmwasm_client::types::ExecuteResult;
|
||||
use validator_client::nymd::TxResponse;
|
||||
@@ -15,31 +15,23 @@ pub struct SendTxResult {
|
||||
pub block_height: u64,
|
||||
pub code: u32,
|
||||
pub details: TransactionDetails,
|
||||
pub gas_used: u64,
|
||||
pub gas_wanted: u64,
|
||||
pub gas_used: Gas,
|
||||
pub gas_wanted: Gas,
|
||||
pub tx_hash: String,
|
||||
// pub fee: MajorCurrencyAmount,
|
||||
pub fee: Option<DecCoin>,
|
||||
}
|
||||
|
||||
impl SendTxResult {
|
||||
pub fn new(
|
||||
t: TxResponse,
|
||||
details: TransactionDetails,
|
||||
_denom_minor: &str,
|
||||
) -> Result<SendTxResult, TypesError> {
|
||||
Ok(SendTxResult {
|
||||
pub fn new(t: TxResponse, details: TransactionDetails, fee: Option<DecCoin>) -> SendTxResult {
|
||||
SendTxResult {
|
||||
block_height: t.height.value(),
|
||||
code: t.tx_result.code.value(),
|
||||
details,
|
||||
gas_used: t.tx_result.gas_used.value(),
|
||||
gas_wanted: t.tx_result.gas_wanted.value(),
|
||||
gas_used: t.tx_result.gas_used.into(),
|
||||
gas_wanted: t.tx_result.gas_wanted.into(),
|
||||
tx_hash: t.hash.to_string(),
|
||||
// that is completely wrong: fee is what you told the validator to use beforehand
|
||||
// fee: MajorCurrencyAmount::from_decimal_and_denom(
|
||||
// Decimal::new(Uint128::from(t.tx_result.gas_used.value())),
|
||||
// denom_minor.to_string(),
|
||||
// )?,
|
||||
})
|
||||
fee,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,11 +42,21 @@ impl SendTxResult {
|
||||
)]
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct TransactionDetails {
|
||||
pub amount: MajorCurrencyAmount,
|
||||
pub amount: DecCoin,
|
||||
pub from_address: String,
|
||||
pub to_address: String,
|
||||
}
|
||||
|
||||
impl TransactionDetails {
|
||||
pub fn new(amount: DecCoin, from_address: String, to_address: String) -> Self {
|
||||
TransactionDetails {
|
||||
amount,
|
||||
from_address,
|
||||
to_address,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
@@ -66,18 +68,16 @@ pub struct TransactionExecuteResult {
|
||||
pub data_json: String,
|
||||
pub transaction_hash: String,
|
||||
pub gas_info: GasInfo,
|
||||
pub fee: MajorCurrencyAmount,
|
||||
pub fee: Option<DecCoin>,
|
||||
}
|
||||
|
||||
impl TransactionExecuteResult {
|
||||
pub fn from_execute_result(
|
||||
value: ExecuteResult,
|
||||
denom_minor: &str,
|
||||
fee: Option<DecCoin>,
|
||||
) -> Result<TransactionExecuteResult, TypesError> {
|
||||
let gas_info = GasInfo::from_validator_client_gas_info(value.gas_info, denom_minor)?;
|
||||
let fee = gas_info.fee.clone();
|
||||
Ok(TransactionExecuteResult {
|
||||
gas_info,
|
||||
gas_info: value.gas_info.into(),
|
||||
transaction_hash: value.transaction_hash.to_string(),
|
||||
data_json: ::serde_json::to_string_pretty(&value.data)?,
|
||||
logs_json: ::serde_json::to_string_pretty(&value.logs)?,
|
||||
@@ -97,30 +97,24 @@ pub struct RpcTransactionResponse {
|
||||
pub tx_result_json: String,
|
||||
pub block_height: u64,
|
||||
pub transaction_hash: String,
|
||||
pub gas_info: GasInfo,
|
||||
// pub fee: MajorCurrencyAmount,
|
||||
pub gas_used: Gas,
|
||||
pub gas_wanted: Gas,
|
||||
pub fee: Option<DecCoin>,
|
||||
}
|
||||
|
||||
impl RpcTransactionResponse {
|
||||
pub fn from_tx_response(
|
||||
value: &TxResponse,
|
||||
denom_minor: &str,
|
||||
t: &TxResponse,
|
||||
fee: Option<DecCoin>,
|
||||
) -> Result<RpcTransactionResponse, TypesError> {
|
||||
Ok(RpcTransactionResponse {
|
||||
index: value.index,
|
||||
gas_info: GasInfo::from_u64(
|
||||
value.tx_result.gas_wanted.value(),
|
||||
value.tx_result.gas_used.value(),
|
||||
denom_minor,
|
||||
)?,
|
||||
transaction_hash: value.hash.to_string(),
|
||||
tx_result_json: ::serde_json::to_string_pretty(&value.tx_result)?,
|
||||
block_height: value.height.value(),
|
||||
// wrong
|
||||
// fee: MajorCurrencyAmount::from_decimal_and_denom(
|
||||
// Decimal::new(Uint128::from(value.tx_result.gas_used.value())),
|
||||
// denom_minor.to_string(),
|
||||
// )?,
|
||||
index: t.index,
|
||||
gas_used: t.tx_result.gas_used.into(),
|
||||
gas_wanted: t.tx_result.gas_wanted.into(),
|
||||
transaction_hash: t.hash.to_string(),
|
||||
tx_result_json: ::serde_json::to_string_pretty(&t.tx_result)?,
|
||||
block_height: t.height.value(),
|
||||
fee,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::currency::{DecCoin, RegisteredCoins};
|
||||
use crate::error::TypesError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vesting_contract::vesting::Account as VestingAccount;
|
||||
use vesting_contract::vesting::VestingPeriod as VestingVestingPeriod;
|
||||
use vesting_contract_common::OriginalVestingResponse as VestingOriginalVestingResponse;
|
||||
use vesting_contract_common::PledgeData as VestingPledgeData;
|
||||
use vesting_contract::vesting::Account as ContractVestingAccount;
|
||||
use vesting_contract::vesting::VestingPeriod as ContractVestingPeriod;
|
||||
use vesting_contract_common::OriginalVestingResponse as ContractOriginalVestingResponse;
|
||||
use vesting_contract_common::PledgeData as ContractPledgeData;
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
@@ -13,25 +13,19 @@ use vesting_contract_common::PledgeData as VestingPledgeData;
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct PledgeData {
|
||||
pub amount: MajorCurrencyAmount,
|
||||
pub amount: DecCoin,
|
||||
pub block_time: u64,
|
||||
}
|
||||
|
||||
impl TryFrom<VestingPledgeData> for PledgeData {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(data: VestingPledgeData) -> Result<Self, Self::Error> {
|
||||
let amount: MajorCurrencyAmount = data.amount().into();
|
||||
Ok(Self {
|
||||
amount,
|
||||
block_time: data.block_time().seconds(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PledgeData {
|
||||
pub fn and_then(data: VestingPledgeData) -> Option<Self> {
|
||||
data.try_into().ok()
|
||||
pub fn from_vesting_contract(
|
||||
pledge: ContractPledgeData,
|
||||
reg: &RegisteredCoins,
|
||||
) -> Result<Self, TypesError> {
|
||||
Ok(PledgeData {
|
||||
amount: reg.attempt_convert_to_display_dec_coin(pledge.amount.into())?,
|
||||
block_time: pledge.block_time.seconds(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,20 +36,20 @@ impl PledgeData {
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct OriginalVestingResponse {
|
||||
amount: MajorCurrencyAmount,
|
||||
amount: DecCoin,
|
||||
number_of_periods: usize,
|
||||
period_duration: u64,
|
||||
}
|
||||
|
||||
impl TryFrom<VestingOriginalVestingResponse> for OriginalVestingResponse {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(data: VestingOriginalVestingResponse) -> Result<Self, Self::Error> {
|
||||
let amount = data.amount().into();
|
||||
Ok(Self {
|
||||
amount,
|
||||
number_of_periods: data.number_of_periods(),
|
||||
period_duration: data.period_duration(),
|
||||
impl OriginalVestingResponse {
|
||||
pub fn from_vesting_contract(
|
||||
res: ContractOriginalVestingResponse,
|
||||
reg: &RegisteredCoins,
|
||||
) -> Result<Self, TypesError> {
|
||||
Ok(OriginalVestingResponse {
|
||||
amount: reg.attempt_convert_to_display_dec_coin(res.amount.into())?,
|
||||
number_of_periods: res.number_of_periods,
|
||||
period_duration: res.period_duration,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -71,24 +65,20 @@ pub struct VestingAccountInfo {
|
||||
staking_address: Option<String>,
|
||||
start_time: u64,
|
||||
periods: Vec<VestingPeriod>,
|
||||
amount: MajorCurrencyAmount,
|
||||
amount: DecCoin,
|
||||
}
|
||||
|
||||
impl TryFrom<VestingAccount> for VestingAccountInfo {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(account: VestingAccount) -> Result<Self, Self::Error> {
|
||||
let mut periods = Vec::new();
|
||||
for period in account.periods() {
|
||||
periods.push(period.into());
|
||||
}
|
||||
let amount: MajorCurrencyAmount = account.coin().into();
|
||||
Ok(Self {
|
||||
impl VestingAccountInfo {
|
||||
pub fn from_vesting_contract(
|
||||
account: ContractVestingAccount,
|
||||
reg: &RegisteredCoins,
|
||||
) -> Result<Self, TypesError> {
|
||||
Ok(VestingAccountInfo {
|
||||
owner_address: account.owner_address().to_string(),
|
||||
staking_address: account.staking_address().map(|a| a.to_string()),
|
||||
start_time: account.start_time().seconds(),
|
||||
periods,
|
||||
amount,
|
||||
periods: account.periods().into_iter().map(Into::into).collect(),
|
||||
amount: reg.attempt_convert_to_display_dec_coin(account.coin.into())?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -104,8 +94,8 @@ pub struct VestingPeriod {
|
||||
period_seconds: u64,
|
||||
}
|
||||
|
||||
impl From<VestingVestingPeriod> for VestingPeriod {
|
||||
fn from(period: VestingVestingPeriod) -> Self {
|
||||
impl From<ContractVestingPeriod> for VestingPeriod {
|
||||
fn from(period: ContractVestingPeriod) -> Self {
|
||||
Self {
|
||||
start_time: period.start_time,
|
||||
period_seconds: period.period_seconds,
|
||||
|
||||
@@ -1195,7 +1195,7 @@ mod tests {
|
||||
|
||||
// _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
// let delegation = query_mixnode_delegation(
|
||||
// let _delegation = query_mixnode_delegation(
|
||||
// &deps.storage,
|
||||
// &deps.api,
|
||||
// identity.clone(),
|
||||
|
||||
@@ -1225,7 +1225,6 @@ pub mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
|
||||
env.block.height += 2 * constants::MINIMUM_BLOCK_AGE_FOR_REWARDING;
|
||||
|
||||
let mix_1 = mixnodes_storage::read_full_mixnode_bond(&deps.storage, &node_identity_1)
|
||||
@@ -1531,7 +1530,7 @@ pub mod tests {
|
||||
assert_eq!(mix_2_reward_result.lambda(), U128::from_num(0.0001f64));
|
||||
assert_eq!(mix_2_reward_result.reward().int(), 974456u128);
|
||||
|
||||
let mix_3_reward_result = mix_3.reward(¶ms3);
|
||||
let _mix_3_reward_result = mix_3.reward(¶ms3);
|
||||
|
||||
// assert_eq!(mix_3_reward_result.reward().int(), mix_1_reward_result.reward().int() + mix_2_reward_result.reward().int());
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@ fn generate_storage_key(storage: &mut dyn Storage) -> Result<u32, ContractError>
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct Account {
|
||||
owner_address: Addr,
|
||||
staking_address: Option<Addr>,
|
||||
start_time: Timestamp,
|
||||
periods: Vec<VestingPeriod>,
|
||||
coin: Coin,
|
||||
pub owner_address: Addr,
|
||||
pub staking_address: Option<Addr>,
|
||||
pub start_time: Timestamp,
|
||||
pub periods: Vec<VestingPeriod>,
|
||||
pub coin: Coin,
|
||||
storage_key: u32,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
EXPLORER_API_URL=https://qa-explorer.nymtech.net/api/v1
|
||||
VALIDATOR_API_URL=https://qa-validator.nymtech.net
|
||||
BIG_DIPPER_URL=https://qa-blocks.nymtech.net
|
||||
CURRENCY_DENOM=unymt
|
||||
CURRENCY_STAKING_DENOM=unyxt
|
||||
@@ -1,15 +1,32 @@
|
||||
import * as React from 'react';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { ExpandLess, ExpandMore } from '@mui/icons-material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Tooltip } from '@nymproject/react/tooltip/Tooltip';
|
||||
|
||||
export const CustomColumnHeading: React.FC<{ headingTitle: string }> = ({ headingTitle }) => {
|
||||
export const CustomColumnHeading: React.FC<{ headingTitle: string; tooltipInfo?: string }> = ({
|
||||
headingTitle,
|
||||
tooltipInfo,
|
||||
}) => {
|
||||
const [filter, toggleFilter] = React.useState<boolean>(false);
|
||||
const theme = useTheme();
|
||||
|
||||
const handleClick = () => {
|
||||
toggleFilter(!filter);
|
||||
};
|
||||
return (
|
||||
<Box alignItems="center" display="flex" onClick={handleClick}>
|
||||
{tooltipInfo && (
|
||||
<Tooltip
|
||||
title={tooltipInfo}
|
||||
id={headingTitle}
|
||||
placement="top-start"
|
||||
textColor={theme.palette.nym.networkExplorer.tooltip.color}
|
||||
bgColor={theme.palette.nym.networkExplorer.tooltip.background}
|
||||
maxWidth={230}
|
||||
arrow
|
||||
/>
|
||||
)}
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
IconButton,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -12,15 +11,13 @@ import {
|
||||
useMediaQuery,
|
||||
} from '@mui/material';
|
||||
import { Box } from '@mui/system';
|
||||
import { styled, useTheme, Theme } from '@mui/material/styles';
|
||||
import Tooltip, { tooltipClasses, TooltipProps } from '@mui/material/Tooltip';
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
import { useTheme, Theme } from '@mui/material/styles';
|
||||
import { Tooltip } from '@nymproject/react/tooltip/Tooltip';
|
||||
import { EconomicsRowsType, EconomicsInfoRowWithIndex } from './types';
|
||||
import { EconomicsProgress } from './EconomicsProgress';
|
||||
import { cellStyles } from '../../Universal-DataGrid';
|
||||
import { UniversalTableProps } from '../../DetailTable';
|
||||
|
||||
const tooltipBackGroundColor = '#A0AED1';
|
||||
const threshold = 100;
|
||||
|
||||
const textColour = (value: EconomicsRowsType, field: string, theme: Theme) => {
|
||||
@@ -83,16 +80,6 @@ export const DelegatorsInfoTable: React.FC<UniversalTableProps<EconomicsInfoRowW
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const CustomTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||
<Tooltip {...props} classes={{ popper: className }} />
|
||||
))({
|
||||
[`& .${tooltipClasses.tooltip}`]: {
|
||||
maxWidth: 230,
|
||||
background: tooltipBackGroundColor,
|
||||
color: theme.palette.nym.networkExplorer.nav.hover,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 650 }} aria-label={tableName}>
|
||||
@@ -103,34 +90,15 @@ export const DelegatorsInfoTable: React.FC<UniversalTableProps<EconomicsInfoRowW
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
{tooltipInfo && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<CustomTooltip
|
||||
<Tooltip
|
||||
title={tooltipInfo}
|
||||
id={field}
|
||||
placement="top-start"
|
||||
sx={{
|
||||
'& .MuiTooltip-arrow': {
|
||||
color: '#A0AED1',
|
||||
},
|
||||
}}
|
||||
textColor={theme.palette.nym.networkExplorer.tooltip.color}
|
||||
bgColor={theme.palette.nym.networkExplorer.tooltip.background}
|
||||
maxWidth={230}
|
||||
arrow
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
padding: 0,
|
||||
py: 1,
|
||||
pr: 1,
|
||||
}}
|
||||
disableFocusRipple
|
||||
disableRipple
|
||||
>
|
||||
<InfoOutlinedIcon
|
||||
sx={{
|
||||
height: '18px',
|
||||
width: '18px',
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</CustomTooltip>
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{title}
|
||||
|
||||
@@ -15,15 +15,13 @@ import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import { NymLogo } from '@nymproject/react/logo/NymLogo';
|
||||
import { BIG_DIPPER, NYM_WEBSITE } from '../api/constants';
|
||||
import { NYM_WEBSITE } from '../api/constants';
|
||||
import { useMainContext } from '../context/main';
|
||||
import { MobileDrawerClose } from '../icons/MobileDrawerClose';
|
||||
import { OverviewSVG } from '../icons/OverviewSVG';
|
||||
import { NetworkComponentsSVG } from '../icons/NetworksSVG';
|
||||
import { NodemapSVG } from '../icons/NodemapSVG';
|
||||
import { Socials } from './Socials';
|
||||
import { Footer } from './Footer';
|
||||
import { DarkLightSwitchDesktop } from './Switch';
|
||||
import { NavOptionType } from '../context/nav';
|
||||
|
||||
const drawerWidth = 300;
|
||||
|
||||
@@ -70,57 +68,6 @@ const Drawer = styled(MuiDrawer, {
|
||||
}),
|
||||
}));
|
||||
|
||||
type NavOptionType = {
|
||||
id: number;
|
||||
isActive?: boolean;
|
||||
url: string;
|
||||
title: string;
|
||||
Icon?: React.ReactNode;
|
||||
nested?: NavOptionType[];
|
||||
isExpandedChild?: boolean;
|
||||
};
|
||||
|
||||
export const originalNavOptions: NavOptionType[] = [
|
||||
{
|
||||
id: 0,
|
||||
isActive: false,
|
||||
url: '/',
|
||||
title: 'Overview',
|
||||
Icon: <OverviewSVG />,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
isActive: false,
|
||||
url: '/network-components',
|
||||
title: 'Network Components',
|
||||
Icon: <NetworkComponentsSVG />,
|
||||
nested: [
|
||||
{
|
||||
id: 3,
|
||||
url: '/network-components/mixnodes',
|
||||
title: 'Mixnodes',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
url: '/network-components/gateways',
|
||||
title: 'Gateways',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
url: `${BIG_DIPPER}/validators`,
|
||||
title: 'Validators',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
isActive: false,
|
||||
url: '/nodemap',
|
||||
title: 'Nodemap',
|
||||
Icon: <NodemapSVG />,
|
||||
},
|
||||
];
|
||||
|
||||
type ExpandableButtonType = {
|
||||
id: number;
|
||||
title: string;
|
||||
@@ -203,14 +150,20 @@ export const ExpandableButton: React.FC<ExpandableButtonType> = ({
|
||||
}
|
||||
}, [drawIsTempOpen]);
|
||||
|
||||
const linkProps = isExternal
|
||||
? {
|
||||
component: 'a',
|
||||
href: url,
|
||||
target: '_blank',
|
||||
}
|
||||
: { component: !nested ? Link : 'div', to: url };
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItem
|
||||
disablePadding
|
||||
disableGutters
|
||||
component={!nested ? Link : 'div'}
|
||||
to={isExternal ? { pathname: url } : url}
|
||||
target={isExternal ? '_blank' : ''}
|
||||
{...linkProps}
|
||||
sx={{
|
||||
borderBottom: isChild ? 'none' : '1px solid rgba(255, 255, 255, 0.1)',
|
||||
...(isActive
|
||||
|
||||
@@ -181,9 +181,14 @@ export const PageMixnodes: React.FC = () => {
|
||||
{
|
||||
field: 'stake_saturation',
|
||||
headerName: 'Stake Saturation',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Stake Saturation" />,
|
||||
renderHeader: () => (
|
||||
<CustomColumnHeading
|
||||
headingTitle="Stake Saturation"
|
||||
tooltipInfo="Level of stake saturation for this node. Nodes receive more rewards the higher their saturation level, up to 100%. Beyond 100% no additional rewards are granted. The current stake saturation level is: 1 million NYM, computed as S/K where S is total amount of tokens available to stakeholders and K is the number of nodes in the reward set."
|
||||
/>
|
||||
),
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
width: 175,
|
||||
width: 190,
|
||||
headerAlign: 'left',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<MuiLink
|
||||
@@ -219,9 +224,14 @@ export const PageMixnodes: React.FC = () => {
|
||||
{
|
||||
field: 'profit_percentage',
|
||||
headerName: 'Profit Margin',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Profit Margin" />,
|
||||
renderHeader: () => (
|
||||
<CustomColumnHeading
|
||||
headingTitle="Profit Margin"
|
||||
tooltipInfo="Percentage of the delegates rewards that the operator takes as fee before rewards are distributed to the delegates."
|
||||
/>
|
||||
),
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
width: 140,
|
||||
width: 165,
|
||||
headerAlign: 'left',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<MuiLink
|
||||
@@ -236,7 +246,12 @@ export const PageMixnodes: React.FC = () => {
|
||||
{
|
||||
field: 'avg_uptime',
|
||||
headerName: 'Routing Score',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Routing Score" />,
|
||||
renderHeader: () => (
|
||||
<CustomColumnHeading
|
||||
headingTitle="Routing Score"
|
||||
tooltipInfo="Node’s routing score is relative to that of the network. Each time a node is tested, the test packets have to go through the full path of the network (a gateway + 3 nodes). If a node in the path drop packets it will affect the score of other nodes in the test."
|
||||
/>
|
||||
),
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
width: 160,
|
||||
headerAlign: 'left',
|
||||
|
||||
@@ -9,15 +9,15 @@ mod commands;
|
||||
mod config;
|
||||
mod node;
|
||||
|
||||
static LONG_ABOUT: OnceCell<String> = OnceCell::new();
|
||||
static LONG_VERSION: OnceCell<String> = OnceCell::new();
|
||||
|
||||
// Helper for passing LONG_ABOUT to clap
|
||||
fn long_about() -> &'static str {
|
||||
LONG_ABOUT.get().expect("Failed to get long about text")
|
||||
fn long_version_static() -> &'static str {
|
||||
LONG_VERSION.get().expect("Failed to get long about text")
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author = "Nymtech", version, about, long_about = Some(long_about()))]
|
||||
#[clap(author = "Nymtech", version, about, long_version = long_version_static())]
|
||||
struct Cli {
|
||||
#[clap(subcommand)]
|
||||
command: commands::Commands,
|
||||
@@ -28,7 +28,7 @@ async fn main() {
|
||||
dotenv::dotenv().ok();
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
LONG_ABOUT
|
||||
LONG_VERSION
|
||||
.set(long_version())
|
||||
.expect("Failed to set long about text");
|
||||
|
||||
@@ -107,3 +107,17 @@ fn setup_logging() {
|
||||
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
|
||||
.init();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use clap::CommandFactory;
|
||||
|
||||
#[test]
|
||||
fn verify_cli() {
|
||||
LONG_VERSION
|
||||
.set(long_version())
|
||||
.expect("Failed to set long about text");
|
||||
Cli::command().debug_assert();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,9 @@ pub(crate) enum RequestHandlingError {
|
||||
#[error("This gateway is not running in the disabled credentials mode")]
|
||||
NotInDisabledCredentialsMode,
|
||||
|
||||
#[error("Nymd Error - {0}")]
|
||||
NymdError(#[from] validator_client::nymd::error::NymdError),
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
#[error("Ethereum web3 error")]
|
||||
Web3Error(#[from] web3::Error),
|
||||
@@ -60,10 +63,6 @@ pub(crate) enum RequestHandlingError {
|
||||
#[error("Ethereum contract error")]
|
||||
EthContractError(#[from] web3::contract::Error),
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
#[error("Nymd Error - {0}")]
|
||||
NymdError(#[from] validator_client::nymd::error::NymdError),
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
#[error("Validator API error")]
|
||||
APIError(#[from] validator_client::ValidatorClientError),
|
||||
@@ -71,6 +70,14 @@ pub(crate) enum RequestHandlingError {
|
||||
#[cfg(feature = "coconut")]
|
||||
#[error("Not enough validator API endpoints provided. Needed {needed}, received {received}")]
|
||||
NotEnoughValidatorAPIs { received: usize, needed: usize },
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
#[error("Validator API {url} misbehaved in the bandwidth redemption protocol: {reason}")]
|
||||
MisbehavingAPI { url: String, reason: String },
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
#[error("Coconut interface error - {0}")]
|
||||
CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError),
|
||||
}
|
||||
|
||||
impl RequestHandlingError {
|
||||
@@ -223,46 +230,9 @@ where
|
||||
));
|
||||
}
|
||||
|
||||
let req = validator_api_requests::coconut::ProposeReleaseFundsRequestBody::new(
|
||||
credential.clone(),
|
||||
);
|
||||
let proposal_id = self
|
||||
.inner
|
||||
.coconut_verifier
|
||||
.api_clients()
|
||||
.get(0)
|
||||
.ok_or(RequestHandlingError::NotEnoughValidatorAPIs {
|
||||
needed: 1,
|
||||
received: 0,
|
||||
})?
|
||||
.propose_release_funds(&req)
|
||||
.await?
|
||||
.proposal_id;
|
||||
|
||||
let req = validator_api_requests::coconut::VerifyCredentialBody::new(
|
||||
credential.clone(),
|
||||
proposal_id,
|
||||
);
|
||||
for client in self.inner.coconut_verifier.api_clients().iter().skip(1) {
|
||||
if !client
|
||||
.verify_bandwidth_credential(&req)
|
||||
.await?
|
||||
.verification_result
|
||||
{
|
||||
debug!("Validator {} didn't accept the credential. It will probably vote No on the spending proposal", client.validator_api.current_url());
|
||||
}
|
||||
}
|
||||
|
||||
let req = validator_api_requests::coconut::ExecuteReleaseFundsRequestBody::new(proposal_id);
|
||||
self.inner
|
||||
.coconut_verifier
|
||||
.api_clients()
|
||||
.get(0)
|
||||
.ok_or(RequestHandlingError::NotEnoughValidatorAPIs {
|
||||
needed: 1,
|
||||
received: 0,
|
||||
})?
|
||||
.execute_release_funds(&req)
|
||||
.release_funds(&credential)
|
||||
.await?;
|
||||
|
||||
let bandwidth = Bandwidth::from(credential);
|
||||
|
||||
@@ -1,40 +1,134 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use coconut_interface::VerificationKey;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use log::*;
|
||||
|
||||
use coconut_interface::{Credential, VerificationKey};
|
||||
use network_defaults::MIX_DENOM;
|
||||
use validator_client::{
|
||||
nymd::{NymdClient, SigningNymdClient},
|
||||
nymd::{
|
||||
traits::{MultisigSigningClient, QueryClient},
|
||||
Coin, Fee, NymdClient, SigningNymdClient,
|
||||
},
|
||||
ApiClient,
|
||||
};
|
||||
|
||||
pub struct CoconutVerifier {
|
||||
use super::authenticated::RequestHandlingError;
|
||||
|
||||
const ONE_HOUR_SEC: u64 = 3600;
|
||||
const MAX_FEEGRANT_UNYM: u128 = 10000;
|
||||
|
||||
pub(crate) struct CoconutVerifier {
|
||||
api_clients: Vec<ApiClient>,
|
||||
_nymd_client: NymdClient<SigningNymdClient>,
|
||||
nymd_client: NymdClient<SigningNymdClient>,
|
||||
aggregated_verification_key: VerificationKey,
|
||||
}
|
||||
|
||||
impl CoconutVerifier {
|
||||
pub fn new(
|
||||
api_clients: Vec<ApiClient>,
|
||||
_nymd_client: NymdClient<SigningNymdClient>,
|
||||
nymd_client: NymdClient<SigningNymdClient>,
|
||||
aggregated_verification_key: VerificationKey,
|
||||
) -> Self {
|
||||
CoconutVerifier {
|
||||
api_clients,
|
||||
_nymd_client,
|
||||
aggregated_verification_key,
|
||||
) -> Result<Self, RequestHandlingError> {
|
||||
if api_clients.is_empty() {
|
||||
return Err(RequestHandlingError::NotEnoughValidatorAPIs {
|
||||
received: 0,
|
||||
needed: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn api_clients(&self) -> &Vec<ApiClient> {
|
||||
&self.api_clients
|
||||
}
|
||||
|
||||
pub fn _nymd_client(&self) -> &NymdClient<SigningNymdClient> {
|
||||
&self._nymd_client
|
||||
Ok(CoconutVerifier {
|
||||
api_clients,
|
||||
nymd_client,
|
||||
aggregated_verification_key,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn aggregated_verification_key(&self) -> &VerificationKey {
|
||||
&self.aggregated_verification_key
|
||||
}
|
||||
|
||||
pub async fn release_funds(&self, credential: &Credential) -> Result<(), RequestHandlingError> {
|
||||
// Use a custom multiplier for revoke, as the default one (1.3)
|
||||
// isn't enough
|
||||
let revoke_fee = Some(Fee::Auto(Some(1.5)));
|
||||
|
||||
let first_api_client = self
|
||||
.api_clients
|
||||
.get(0)
|
||||
.expect("This shouldn't happen, as we check for length in constructor");
|
||||
|
||||
let first_api_cosmos_addr = first_api_client.get_cosmos_address().await?.addr;
|
||||
self.nymd_client
|
||||
.grant_allowance(
|
||||
&first_api_cosmos_addr,
|
||||
vec![Coin::new(MAX_FEEGRANT_UNYM, MIX_DENOM.base)],
|
||||
SystemTime::now().checked_add(Duration::from_secs(ONE_HOUR_SEC)),
|
||||
// It would be nice to be able to filter deeper, but for now only the msg type filter is avaialable
|
||||
vec![String::from("/cosmwasm.wasm.v1.MsgExecuteContract")],
|
||||
"Create allowance to propose the release of funds".to_string(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let req = validator_api_requests::coconut::ProposeReleaseFundsRequestBody::new(
|
||||
credential.clone(),
|
||||
self.nymd_client.address().clone(),
|
||||
);
|
||||
let ret = first_api_client.propose_release_funds(&req).await;
|
||||
|
||||
self.nymd_client
|
||||
.revoke_allowance(
|
||||
&first_api_cosmos_addr,
|
||||
"Cleanup the previous allowance for releasing funds".to_string(),
|
||||
revoke_fee.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let proposal_id = ret?.proposal_id;
|
||||
|
||||
let proposal = self.nymd_client.get_proposal(proposal_id).await?;
|
||||
if !credential.has_blinded_serial_number(&proposal.description)? {
|
||||
return Err(RequestHandlingError::MisbehavingAPI {
|
||||
url: first_api_client.validator_api.current_url().to_string(),
|
||||
reason: String::from("Created proposal with different serial number"),
|
||||
});
|
||||
}
|
||||
|
||||
let req = validator_api_requests::coconut::VerifyCredentialBody::new(
|
||||
credential.clone(),
|
||||
proposal_id,
|
||||
self.nymd_client.address().clone(),
|
||||
);
|
||||
for client in self.api_clients.iter().skip(1) {
|
||||
let api_cosmos_addr = client.get_cosmos_address().await?.addr;
|
||||
self.nymd_client
|
||||
.grant_allowance(
|
||||
&api_cosmos_addr,
|
||||
vec![Coin::new(MAX_FEEGRANT_UNYM, MIX_DENOM.base)],
|
||||
SystemTime::now().checked_add(Duration::from_secs(ONE_HOUR_SEC)),
|
||||
// It would be nice to be able to filter deeper, but for now only the msg type filter is avaialable
|
||||
vec![String::from("/cosmwasm.wasm.v1.MsgExecuteContract")],
|
||||
"Create allowance to vote the release of funds".to_string(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let ret = client.verify_bandwidth_credential(&req).await;
|
||||
self.nymd_client
|
||||
.revoke_allowance(
|
||||
&api_cosmos_addr,
|
||||
"Cleanup the previous allowance for releasing funds".to_string(),
|
||||
revoke_fee.clone(),
|
||||
)
|
||||
.await?;
|
||||
if !ret?.verification_result {
|
||||
debug!("Validator {} didn't accept the credential. It will probably vote No on the spending proposal", client.validator_api.current_url());
|
||||
}
|
||||
}
|
||||
|
||||
self.nymd_client.execute_proposal(proposal_id, None).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +317,8 @@ where
|
||||
self.all_api_clients(),
|
||||
nymd_client,
|
||||
validators_verification_key,
|
||||
);
|
||||
)
|
||||
.expect("Could not create coconut verifier");
|
||||
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let erc20_bridge = ERC20Bridge::new(self.config.get_eth_endpoint(), nymd_client);
|
||||
|
||||
@@ -13,16 +13,16 @@ mod config;
|
||||
mod node;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref LONG_ABOUT: String = long_version();
|
||||
pub static ref LONG_VERSION: String = long_version();
|
||||
}
|
||||
|
||||
// Helper for passing LONG_ABOUT to clap
|
||||
fn long_about() -> &'static str {
|
||||
&LONG_ABOUT
|
||||
// Helper for passing LONG_VERSION to clap
|
||||
fn long_version_static() -> &'static str {
|
||||
&LONG_VERSION
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author = "Nymtech", version, about, long_about = Some(long_about()))]
|
||||
#[clap(author = "Nymtech", version, about, long_version = long_version_static())]
|
||||
struct Cli {
|
||||
#[clap(subcommand)]
|
||||
command: commands::Commands,
|
||||
@@ -106,3 +106,14 @@ fn setup_logging() {
|
||||
.filter_module("want", log::LevelFilter::Warn)
|
||||
.init();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use clap::CommandFactory;
|
||||
|
||||
#[test]
|
||||
fn verify_cli() {
|
||||
Cli::command().debug_assert();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,112 +1,8 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"jest": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": 2019,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"globals": {
|
||||
"Atomics": "readonly",
|
||||
"SharedArrayBuffer": "readonly"
|
||||
},
|
||||
"plugins": ["react", "react-hooks", "jsx-a11y", "prettier", "jest"],
|
||||
"extends": [
|
||||
"plugin:react/recommended",
|
||||
"airbnb",
|
||||
"prettier",
|
||||
"plugin:jest/recommended",
|
||||
"plugin:jest/style"
|
||||
"@nymproject/eslint-config-react-typescript"
|
||||
],
|
||||
"rules": {
|
||||
"jest/prefer-strict-equal": "error",
|
||||
"jest/prefer-to-have-length": "warn",
|
||||
"prettier/prettier": "error",
|
||||
"import/prefer-default-export": "off",
|
||||
"react/prop-types": "off",
|
||||
"react/require-default-props": "off",
|
||||
"react/jsx-filename-extension": "off",
|
||||
"react/jsx-props-no-spreading": "off",
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
"devDependencies": [
|
||||
"**/*.test.[jt]s",
|
||||
"**/*.spec.[jt]s",
|
||||
"**/*.test.[jt]sx",
|
||||
"**/*.spec.[jt]sx"
|
||||
]
|
||||
}
|
||||
],
|
||||
"import/extensions": [
|
||||
"error",
|
||||
"ignorePackages",
|
||||
{
|
||||
"ts": "never",
|
||||
"tsx": "never",
|
||||
"js": "never",
|
||||
"jsx": "never"
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": "**/*.+(ts|tsx)",
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"plugins": ["@typescript-eslint/eslint-plugin"],
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"no-use-before-define": [0],
|
||||
"@typescript-eslint/no-use-before-define": [1],
|
||||
"import/no-unresolved": 0,
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
"devDependencies": [
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.stories.*",
|
||||
"**/.storybook/**/*.*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"quotes": "off",
|
||||
"@typescript-eslint/quotes": [
|
||||
2,
|
||||
"single",
|
||||
{
|
||||
"avoidEscape": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }]
|
||||
}
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"root-import": {
|
||||
"rootPathPrefix": "@",
|
||||
"rootPathSuffix": "src",
|
||||
"extensions": [".js", ".ts", ".tsx", ".jsx", ".mdx"]
|
||||
}
|
||||
}
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.eslint.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,60 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
stories: [
|
||||
"../src/**/*.stories.mdx",
|
||||
"../src/**/*.stories.@(js|jsx|ts|tsx)"
|
||||
],
|
||||
addons: [
|
||||
"@storybook/addon-links",
|
||||
"@storybook/addon-essentials"
|
||||
],
|
||||
framework: "@storybook/react",
|
||||
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||
addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||
framework: '@storybook/react',
|
||||
core: {
|
||||
builder: 'webpack5',
|
||||
},
|
||||
typescript: { reactDocgen: false },
|
||||
// webpackFinal: async (config, { configType }) => {
|
||||
// // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
|
||||
// // You can change the configuration based on that.
|
||||
// // 'PRODUCTION' is used when building the static version of storybook.
|
||||
webpackFinal: async (config) => {
|
||||
config.module.rules.forEach((rule) => {
|
||||
// look for SVG import rule and replace
|
||||
// NOTE: the rule before modification is /\.(svg|ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/
|
||||
if (rule.test?.toString().includes('svg')) {
|
||||
rule.test = /\.(ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/;
|
||||
}
|
||||
});
|
||||
|
||||
// handle asset loading with this
|
||||
config.module.rules.unshift({
|
||||
test: /\.svg(\?.*)?$/i,
|
||||
issuer: /\.[jt]sx?$/,
|
||||
use: ['@svgr/webpack'],
|
||||
});
|
||||
|
||||
config.resolve.extensions = ['.tsx', '.ts', '.js'];
|
||||
config.resolve.plugins = [new TsconfigPathsPlugin()];
|
||||
|
||||
config.plugins.push(
|
||||
new ForkTsCheckerWebpackPlugin({
|
||||
typescript: {
|
||||
mode: 'write-references',
|
||||
diagnosticOptions: {
|
||||
semantic: true,
|
||||
syntactic: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
if (!config.resolve.alias) {
|
||||
config.resolve.alias = {};
|
||||
}
|
||||
|
||||
config.resolve.alias['@tauri-apps/api'] = `${__dirname}/mocks/tauri`;
|
||||
|
||||
// Return the altered config
|
||||
return config;
|
||||
},
|
||||
features: {
|
||||
emotionAlias: false,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* This is a mock for Tauri's API package (@tauri-apps/api/app), to prevent stories from being excluded, because they either use
|
||||
* or import dependencies that use Tauri.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
getVersion: () => undefined,
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* This is a mock for Tauri's API package (@tauri-apps/api/clipboard), to prevent stories from being excluded, because they either use
|
||||
* or import dependencies that use Tauri.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
writeText: async () => undefined,
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* This is a mock for Tauri's API package (@tauri-apps/api/clipboard), to prevent stories from being excluded, because they either use
|
||||
* or import dependencies that use Tauri.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
listen: async () => undefined,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* This is a mock for Tauri's API package (@tauri-apps/api), to prevent stories from being excluded, because they either use
|
||||
* or import dependencies that use Tauri.
|
||||
*/
|
||||
module.exports = {
|
||||
invoke: (operation, args) => {
|
||||
console.error(
|
||||
`Tauri cannot be used in Storybook. The operation requested was "${operation}". You can add mock responses to "nym_connect/.storybook/mocks/tauri.js" if you need. The default response is "void".`,
|
||||
);
|
||||
return new Promise((resolve, reject) => {
|
||||
reject(new Error(`Tauri operation ${operation} not available in storybook.`));
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* This is a mock for Tauri's API package (@tauri-apps/api/window), to prevent stories from being excluded, because they either use
|
||||
* or import dependencies that use Tauri.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
appWindow: {
|
||||
maximize: () => undefined,
|
||||
}
|
||||
}
|
||||
@@ -4,33 +4,36 @@
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prewebpack:dev": "yarn --cwd .. build",
|
||||
"webpack:dev": "yarn webpack serve --config webpack.dev.js",
|
||||
"webpack:prod": "yarn webpack --progress --config webpack.prod.js",
|
||||
"tauri:dev": "RUST_DEBUG=1 yarn tauri dev",
|
||||
"tauri:build": "yarn tauri build",
|
||||
"dev": "run-p webpack:dev tauri:dev",
|
||||
"prebuild": "yarn --cwd .. build",
|
||||
"build": "run-s webpack:prod tauri:build",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"prestorybook:build": "yarn --cwd .. build",
|
||||
"storybook:build": "build-storybook",
|
||||
"tsc": "tsc --noEmit true",
|
||||
"tsc:watch": "tsc --noEmit true --watch",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@emotion/react": "^11.7.0",
|
||||
"@emotion/styled": "^11.6.0",
|
||||
"@hookform/resolvers": "^2.8.0",
|
||||
"@mui/icons-material": "^5.2.0",
|
||||
"@mui/material": "^5.2.2",
|
||||
"@mui/styles": "^5.2.2",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"bs58": "^4.0.1",
|
||||
"@nymproject/react": "^1.0.0",
|
||||
"@tauri-apps/api": "^1.0.2",
|
||||
"clsx": "^1.1.1",
|
||||
"luxon": "^2.3.0",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
"qrcode.react": "^1.0.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-error-boundary": "^3.1.3",
|
||||
@@ -44,48 +47,63 @@
|
||||
"@babel/plugin-transform-async-to-generator": "^7.14.5",
|
||||
"@babel/preset-env": "^7.15.0",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@storybook/addon-actions": "^6.4.18",
|
||||
"@storybook/addon-essentials": "^6.4.18",
|
||||
"@storybook/addon-links": "^6.4.18",
|
||||
"@storybook/react": "^6.4.18",
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
||||
"@storybook/react": "^6.5.8",
|
||||
"@svgr/webpack": "^6.1.1",
|
||||
"@tauri-apps/api": "^1.0.0-rc.4",
|
||||
"@tauri-apps/cli": "^1.0.0-rc.9",
|
||||
"@types/bs58": "^4.0.1",
|
||||
"@types/luxon": "^2.0.9",
|
||||
"@types/qrcode.react": "^1.0.2",
|
||||
"@types/react-router-dom": "^5.1.8",
|
||||
"@tauri-apps/cli": "^1.0.2",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^12.0.0",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/luxon": "^2.3.2",
|
||||
"@types/node": "^16.7.13",
|
||||
"@types/react": "^17.0.34",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"@types/semver": "^7.3.8",
|
||||
"@typescript-eslint/eslint-plugin": "4.31.0",
|
||||
"@typescript-eslint/parser": "4.31.0",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.13.0",
|
||||
"@typescript-eslint/parser": "^5.13.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-plugin-root-import": "^6.6.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"css-loader": "^6.2.0",
|
||||
"css-minimizer-webpack-plugin": "^3.0.2",
|
||||
"dotenv-webpack": "^7.0.3",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-airbnb": "18.2.1",
|
||||
"eslint-config-prettier": "8.3.0",
|
||||
"eslint-import-resolver-root-import": "1.0.4",
|
||||
"eslint-plugin-import": "2.24.2",
|
||||
"eslint-plugin-jest": "^24.4.0",
|
||||
"eslint-plugin-jsx-a11y": "6.4.1",
|
||||
"eslint-plugin-prettier": "4.0.0",
|
||||
"eslint-plugin-react": "7.25.1",
|
||||
"eslint-plugin-react-hooks": "4.2.0",
|
||||
"eslint": "^8.10.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^16.1.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-import-resolver-root-import": "^1.0.4",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-jest": "^26.1.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.29.2",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-plugin-storybook": "^0.5.12",
|
||||
"favicons": "^6.2.2",
|
||||
"favicons-webpack-plugin": "^5.0.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"fork-ts-checker-webpack-plugin": "^7.2.1",
|
||||
"html-webpack-plugin": "^5.3.2",
|
||||
"jest": "^27.1.0",
|
||||
"mini-css-extract-plugin": "^2.2.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "2.3.2",
|
||||
"react-refresh": "^0.10.0",
|
||||
"react-refresh-typescript": "^2.0.2",
|
||||
"style-loader": "^3.2.1",
|
||||
"thread-loader": "^3.0.4",
|
||||
"ts-jest": "^27.0.5",
|
||||
"typescript": "^4.4.2",
|
||||
"ts-loader": "^9.2.5",
|
||||
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
||||
"typescript": "^4.6.2",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.64.3",
|
||||
"webpack-cli": "^4.8.0",
|
||||
"webpack-dev-server": "^4.5.0",
|
||||
"webpack-favicons": "^1.3.8",
|
||||
"webpack-merge": "^5.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,29 +13,30 @@ rust-version = "1.58"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "=1.0.0-rc.7", features = [] }
|
||||
tauri-build = { version = "^1.0.2", features = [] }
|
||||
|
||||
tauri-codegen = "=1.0.0-rc.5"
|
||||
tauri-macros = "=1.0.0-rc.5"
|
||||
tauri-codegen = "^1.0.2"
|
||||
tauri-macros = "^1.0.2"
|
||||
|
||||
[dependencies]
|
||||
|
||||
bip39 = "1.0"
|
||||
dirs = "4.0"
|
||||
eyre = "0.6.5"
|
||||
fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs", branch = "release"}
|
||||
futures = "0.3"
|
||||
rand = "0.7"
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.4.0"
|
||||
rand = "0.8"
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tauri = { version = "=1.0.0-rc.8", features = ["ayatana-tray", "shell-open", "system-tray"] }
|
||||
tap = "1.0.1"
|
||||
tauri = { version = "^1.0.2", features = ["clipboard-write-text", "shell-open", "system-tray", "updater"] }
|
||||
tendermint-rpc = "0.23.0"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.19.1", features = ["sync", "time"] }
|
||||
url = "2.2"
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.4.0"
|
||||
fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs", branch = "release"}
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
|
||||
client-core = { path = "../../clients/client-core" }
|
||||
config = { path = "../../common/config" }
|
||||
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 60 KiB |
@@ -1,3 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
tauri_build::build();
|
||||
}
|
||||
|
||||
@@ -1,46 +1,41 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use client_core::config::GatewayEndpoint;
|
||||
use log::info;
|
||||
use std::sync::Arc;
|
||||
use tap::TapFallible;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use client_core::config::Config as BaseConfig;
|
||||
use config::NymConfig;
|
||||
use nym_socks5::client::config::Config as Socks5Config;
|
||||
|
||||
use crate::{error::BackendError, state::State};
|
||||
use crate::{
|
||||
error::{BackendError, Result},
|
||||
state::State,
|
||||
};
|
||||
|
||||
pub static SOCKS5_CONFIG_ID: &str = "nym-connect";
|
||||
static SOCKS5_CONFIG_ID: &str = "nym-connect";
|
||||
|
||||
const DEFAULT_ETH_ENDPOINT: &str = "https://rinkeby.infura.io/v3/00000000000000000000000000000000";
|
||||
const DEFAULT_ETH_PRIVATE_KEY: &str =
|
||||
"0000000000000000000000000000000000000000000000000000000000000001";
|
||||
|
||||
pub fn append_config_id(gateway_id: &str) -> String {
|
||||
pub fn socks5_config_id_appended_with(gateway_id: &str) -> Result<String> {
|
||||
use std::fmt::Write as _;
|
||||
let mut id = SOCKS5_CONFIG_ID.to_string();
|
||||
write!(id, "-{}", gateway_id).expect("Failed to set config id");
|
||||
id
|
||||
write!(id, "-{}", gateway_id)?;
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_config_id(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<String, BackendError> {
|
||||
let guard = state.read().await;
|
||||
// TODO: return error instead
|
||||
let gateway_id = guard
|
||||
.get_gateway()
|
||||
.as_ref()
|
||||
.expect("The config id can not be determined before setting the gateway");
|
||||
Ok(append_config_id(gateway_id))
|
||||
pub async fn get_config_id(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<String> {
|
||||
state.read().await.get_config_id()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_config_file_location(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<String, BackendError> {
|
||||
) -> Result<String> {
|
||||
let id = get_config_id(state).await?;
|
||||
Ok(Config::config_file_location(&id)
|
||||
.to_string_lossy()
|
||||
@@ -76,21 +71,26 @@ impl Config {
|
||||
self.socks5.get_base_mut()
|
||||
}
|
||||
|
||||
pub async fn init(service_provider: &str, chosen_gateway_id: &str) -> Result<(), BackendError> {
|
||||
info!("Initialising...");
|
||||
pub async fn init(service_provider: &str, chosen_gateway_id: &str) -> Result<()> {
|
||||
log::info!("Initialising...");
|
||||
|
||||
let service_provider = service_provider.to_owned();
|
||||
let chosen_gateway_id = chosen_gateway_id.to_owned();
|
||||
|
||||
// The client initialization was originally not written for this use case, so there are
|
||||
// lots of ways it can panic. Until we have proper error handling in the init code for the
|
||||
// clients we'll catch any panics here.
|
||||
std::panic::catch_unwind(move || {
|
||||
futures::executor::block_on(init_socks5(service_provider, chosen_gateway_id));
|
||||
// clients we'll catch any panics here by spawning a new runtime in a separate thread.
|
||||
std::thread::spawn(move || {
|
||||
tokio::runtime::Runtime::new()
|
||||
.expect("Failed to create tokio runtime")
|
||||
.block_on(
|
||||
async move { init_socks5_config(service_provider, chosen_gateway_id).await },
|
||||
)
|
||||
})
|
||||
.map_err(|_| BackendError::InitializationPanic)?;
|
||||
.join()
|
||||
.map_err(|_| BackendError::InitializationPanic)??;
|
||||
|
||||
info!("Configuration saved 🚀");
|
||||
log::info!("Configuration saved 🚀");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -99,11 +99,11 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn init_socks5(provider_address: String, chosen_gateway_id: String) {
|
||||
log::info!("Initialising client...");
|
||||
pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: String) -> Result<()> {
|
||||
log::trace!("Initialising client...");
|
||||
|
||||
// Append the gateway id to the name id that we store the config under
|
||||
let id = append_config_id(&chosen_gateway_id);
|
||||
let id = socks5_config_id_appended_with(&chosen_gateway_id)?;
|
||||
|
||||
log::debug!(
|
||||
"Attempting to use config file location: {}",
|
||||
@@ -145,10 +145,9 @@ pub async fn init_socks5(provider_address: String, chosen_gateway_id: String) {
|
||||
config.get_base_mut().with_gateway_endpoint(gateway);
|
||||
|
||||
let config_save_location = config.get_socks5().get_config_file_save_location();
|
||||
config
|
||||
.get_socks5()
|
||||
.save_to_file(None)
|
||||
.expect("Failed to save the config file");
|
||||
config.get_socks5().save_to_file(None).tap_err(|_| {
|
||||
log::warn!("Failed to save the config file");
|
||||
})?;
|
||||
|
||||
log::info!("Saved configuration file to {:?}", config_save_location);
|
||||
log::info!("Gateway id: {}", config.get_base().get_gateway_id());
|
||||
@@ -166,9 +165,10 @@ pub async fn init_socks5(provider_address: String, chosen_gateway_id: String) {
|
||||
"Service provider port: {}",
|
||||
config.get_socks5().get_listening_port()
|
||||
);
|
||||
info!("Client configuration completed.");
|
||||
log::info!("Client configuration completed.");
|
||||
|
||||
client_core::init::show_address(config.get_base());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: deduplicate with same functions in other client
|
||||
|
||||
@@ -4,30 +4,64 @@ use thiserror::Error;
|
||||
#[allow(unused)]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum BackendError {
|
||||
#[error("{source}")]
|
||||
ReqwestError {
|
||||
#[from]
|
||||
source: reqwest::Error,
|
||||
},
|
||||
#[error("I/O error: {source}")]
|
||||
IoError {
|
||||
#[from]
|
||||
source: std::io::Error,
|
||||
},
|
||||
#[error("String formatting error: {source}")]
|
||||
FmtError {
|
||||
#[from]
|
||||
source: std::fmt::Error,
|
||||
},
|
||||
#[error("Tauri error: {source}")]
|
||||
TauriError {
|
||||
#[from]
|
||||
source: tauri::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
SerdeJsonError {
|
||||
#[from]
|
||||
source: serde_json::Error,
|
||||
},
|
||||
|
||||
#[error("State error")]
|
||||
StateError,
|
||||
#[error("Could not connect")]
|
||||
CouldNotConnect,
|
||||
#[error("Could not disconnect")]
|
||||
CouldNotDisconnect,
|
||||
#[error("Could not send disconnect signal to the SOCKS5 client")]
|
||||
CoundNotSendDisconnectSignal,
|
||||
#[error("No service provider set")]
|
||||
NoServiceProviderSet,
|
||||
#[error("No gateway provider set")]
|
||||
NoGatewaySet,
|
||||
#[error("{source}")]
|
||||
ReqwestError {
|
||||
#[from]
|
||||
source: reqwest::Error,
|
||||
},
|
||||
#[error("Initialization failed with a panic")]
|
||||
InitializationPanic,
|
||||
#[error("Could not get config id before gateway is set")]
|
||||
CouldNotGetIdWithoutGateway,
|
||||
#[error("Could initialize without gateway set")]
|
||||
CouldNotInitWithoutGateway,
|
||||
#[error("Could initialize without service provider set")]
|
||||
CouldNotInitWithoutServiceProvider,
|
||||
#[error("Could not get file name")]
|
||||
CouldNotGetFilename,
|
||||
}
|
||||
|
||||
impl Serialize for BackendError {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_str(self)
|
||||
}
|
||||
}
|
||||
|
||||
// Local crate level Result alias
|
||||
pub(crate) type Result<T, E = BackendError> = std::result::Result<T, E>;
|
||||
|
||||
@@ -43,8 +43,9 @@ fn main() {
|
||||
crate::operations::connection::connect::set_service_provider,
|
||||
crate::operations::connection::connect::start_connecting,
|
||||
crate::operations::connection::disconnect::start_disconnecting,
|
||||
crate::operations::window::hide_window,
|
||||
crate::operations::directory::get_services,
|
||||
crate::operations::export::export_keys,
|
||||
crate::operations::window::hide_window,
|
||||
])
|
||||
.menu(Menu::new().add_default_app_submenu_if_macos())
|
||||
.system_tray(create_tray_menu())
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use core::fmt;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
@@ -23,6 +25,17 @@ pub enum ConnectionStatusKind {
|
||||
Connecting,
|
||||
}
|
||||
|
||||
impl fmt::Display for ConnectionStatusKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
ConnectionStatusKind::Disconnected => write!(f, "Disconnected"),
|
||||
ConnectionStatusKind::Disconnecting => write!(f, "Disconnecting"),
|
||||
ConnectionStatusKind::Connected => write!(f, "Connected"),
|
||||
ConnectionStatusKind::Connecting => write!(f, "Connecting"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const APP_EVENT_CONNECTION_STATUS_CHANGED: &str = "app:connection-status-changed";
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use crate::{error::BackendError, models::ConnectResult, tasks::start_disconnect_listener, State};
|
||||
use crate::{
|
||||
error::{BackendError, Result},
|
||||
models::ConnectResult,
|
||||
tasks, State,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
@@ -6,30 +10,23 @@ use tokio::sync::RwLock;
|
||||
pub async fn start_connecting(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
window: tauri::Window<tauri::Wry>,
|
||||
) -> Result<ConnectResult, BackendError> {
|
||||
) -> Result<ConnectResult> {
|
||||
let status_receiver = {
|
||||
let mut guard = state.write().await;
|
||||
|
||||
log::trace!("Start connecting with:");
|
||||
log::trace!(" service_provider: {:?}", guard.get_service_provider());
|
||||
log::trace!(" gateway: {:?}", guard.get_gateway());
|
||||
guard.start_connecting(&window).await?
|
||||
let mut state_w = state.write().await;
|
||||
state_w.start_connecting(&window).await?
|
||||
};
|
||||
|
||||
// Setup task for checking status
|
||||
let state = state.inner().clone();
|
||||
start_disconnect_listener(state, window, status_receiver);
|
||||
tasks::start_disconnect_listener(state, window, status_receiver);
|
||||
|
||||
Ok(ConnectResult {
|
||||
// WIP(JON): fixme
|
||||
address: "Test".to_string(),
|
||||
address: "PLACEHOLDER".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_service_provider(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<String, BackendError> {
|
||||
pub async fn get_service_provider(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<String> {
|
||||
let guard = state.read().await;
|
||||
guard
|
||||
.get_service_provider()
|
||||
@@ -41,7 +38,7 @@ pub async fn get_service_provider(
|
||||
pub async fn set_service_provider(
|
||||
service_provider: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
) -> Result<()> {
|
||||
log::trace!("Setting service_provider: {service_provider}");
|
||||
let mut guard = state.write().await;
|
||||
guard.set_service_provider(service_provider);
|
||||
@@ -49,9 +46,7 @@ pub async fn set_service_provider(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_gateway(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<String, BackendError> {
|
||||
pub async fn get_gateway(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<String> {
|
||||
let guard = state.read().await;
|
||||
guard
|
||||
.get_gateway()
|
||||
@@ -63,7 +58,7 @@ pub async fn get_gateway(
|
||||
pub async fn set_gateway(
|
||||
gateway: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<(), BackendError> {
|
||||
) -> Result<()> {
|
||||
log::trace!("Setting gateway: {gateway}");
|
||||
let mut guard = state.write().await;
|
||||
guard.set_gateway(gateway);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::error::BackendError;
|
||||
use crate::error::Result;
|
||||
use crate::models::ConnectResult;
|
||||
use crate::State;
|
||||
use std::sync::Arc;
|
||||
@@ -8,13 +8,12 @@ use tokio::sync::RwLock;
|
||||
pub async fn start_disconnecting(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
window: tauri::Window<tauri::Wry>,
|
||||
) -> Result<ConnectResult, BackendError> {
|
||||
) -> Result<ConnectResult> {
|
||||
let mut guard = state.write().await;
|
||||
|
||||
guard.start_disconnecting(&window).await;
|
||||
guard.start_disconnecting(&window).await?;
|
||||
|
||||
Ok(ConnectResult {
|
||||
// WIP(JON): fixme
|
||||
address: "Test".to_string(),
|
||||
address: "PLACEHOLDER".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::error::BackendError;
|
||||
use crate::error::Result;
|
||||
use crate::models::DirectoryService;
|
||||
|
||||
static SERVICE_PROVIDER_WELLKNOWN_URL: &str =
|
||||
"https://nymtech.net/.wellknown/connect/service-providers.json";
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_services() -> Result<Vec<DirectoryService>, BackendError> {
|
||||
pub async fn get_services() -> Result<Vec<DirectoryService>> {
|
||||
let res = reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL)
|
||||
.await?
|
||||
.json::<Vec<DirectoryService>>()
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
use std::{ffi::OsStr, fs, sync::Arc};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{
|
||||
error::{BackendError, Result},
|
||||
state::State,
|
||||
};
|
||||
|
||||
/// Export the gateway keys as a JSON string blob
|
||||
#[tauri::command]
|
||||
pub async fn export_keys(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<String> {
|
||||
let config = {
|
||||
let state = state.read().await;
|
||||
state.load_socks5_config()?
|
||||
};
|
||||
|
||||
// Get key paths
|
||||
let ack_key_file = config.get_base().get_ack_key_file();
|
||||
let gateway_shared_key_file = config.get_base().get_gateway_shared_key_file();
|
||||
|
||||
let pub_id_key_file = config.get_base().get_public_identity_key_file();
|
||||
let priv_id_key_file = config.get_base().get_private_identity_key_file();
|
||||
|
||||
let pub_enc_key_file = config.get_base().get_public_encryption_key_file();
|
||||
let priv_enc_key_file = config.get_base().get_private_encryption_key_file();
|
||||
|
||||
// Read file contents
|
||||
let ack_key = fs::read_to_string(ack_key_file.clone())?;
|
||||
let gateway_shared_key = fs::read_to_string(gateway_shared_key_file.clone())?;
|
||||
|
||||
let pub_id_key = fs::read_to_string(pub_id_key_file.clone())?;
|
||||
let priv_id_key = fs::read_to_string(priv_id_key_file.clone())?;
|
||||
|
||||
let pub_enc_key = fs::read_to_string(pub_enc_key_file.clone())?;
|
||||
let priv_enc_key = fs::read_to_string(priv_enc_key_file.clone())?;
|
||||
|
||||
let ack_key_file = ack_key_file
|
||||
.file_name()
|
||||
.map(OsStr::to_string_lossy)
|
||||
.ok_or(BackendError::CouldNotGetFilename)?;
|
||||
let gateway_shared_key_file = gateway_shared_key_file
|
||||
.file_name()
|
||||
.map(OsStr::to_string_lossy)
|
||||
.ok_or(BackendError::CouldNotGetFilename)?;
|
||||
let pub_id_key_file = pub_id_key_file
|
||||
.file_name()
|
||||
.map(OsStr::to_string_lossy)
|
||||
.ok_or(BackendError::CouldNotGetFilename)?;
|
||||
let priv_id_key_file = priv_id_key_file
|
||||
.file_name()
|
||||
.map(OsStr::to_string_lossy)
|
||||
.ok_or(BackendError::CouldNotGetFilename)?;
|
||||
let pub_enc_key_file = pub_enc_key_file
|
||||
.file_name()
|
||||
.map(OsStr::to_string_lossy)
|
||||
.ok_or(BackendError::CouldNotGetFilename)?;
|
||||
let priv_enc_key_file = priv_enc_key_file
|
||||
.file_name()
|
||||
.map(OsStr::to_string_lossy)
|
||||
.ok_or(BackendError::CouldNotGetFilename)?;
|
||||
|
||||
// Format and return as json
|
||||
let json = serde_json::json!({
|
||||
ack_key_file: ack_key,
|
||||
gateway_shared_key_file: gateway_shared_key,
|
||||
pub_id_key_file: pub_id_key,
|
||||
priv_id_key_file: priv_id_key,
|
||||
pub_enc_key_file: pub_enc_key,
|
||||
priv_enc_key_file: priv_enc_key,
|
||||
});
|
||||
|
||||
Ok(serde_json::to_string_pretty(&json)?)
|
||||
}
|
||||