Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ae0883241e | |||
| 11baa99c4b | |||
| 8bc23434ab | |||
| 8f6daf1e03 | |||
| 7f63377d22 | |||
| 46cb3eca38 | |||
| 7b22872c6b | |||
| 3342cb13c7 | |||
| 3cd5fc3b22 | |||
| 0a30eb1c64 | |||
| 63bb35e1a1 | |||
| c1e809fd99 | |||
| 77ab22999c | |||
| 747bf85ad8 | |||
| cf4eadaa6b | |||
| f43f07f0b9 | |||
| c5cf7d19a3 | |||
| 7d7911c8e8 | |||
| 40d93e1eeb | |||
| 2f472c4e8e | |||
| 140cc3f769 | |||
| 2045d0bafd | |||
| e68b48f296 | |||
| 3a2b553e38 | |||
| aa1955dc6b |
Generated
+88
-6
@@ -1614,6 +1614,26 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-iterator"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6"
|
||||
dependencies = [
|
||||
"enum-iterator-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-iterator-derive"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum_kind"
|
||||
version = "0.2.1"
|
||||
@@ -2285,6 +2305,19 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.13.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
"libgit2-sys",
|
||||
"log",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib"
|
||||
version = "0.14.5"
|
||||
@@ -3057,6 +3090,18 @@ version = "0.2.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.12.26+1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"libz-sys",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.1"
|
||||
@@ -3074,6 +3119,18 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lioness"
|
||||
version = "0.1.2"
|
||||
@@ -3541,6 +3598,7 @@ dependencies = [
|
||||
"topology",
|
||||
"url",
|
||||
"validator-client",
|
||||
"vergen",
|
||||
"version-checker",
|
||||
"websocket-requests",
|
||||
]
|
||||
@@ -3607,6 +3665,7 @@ dependencies = [
|
||||
"tokio-util",
|
||||
"url",
|
||||
"validator-client",
|
||||
"vergen",
|
||||
"version-checker",
|
||||
"web3",
|
||||
]
|
||||
@@ -3642,6 +3701,7 @@ dependencies = [
|
||||
"topology",
|
||||
"url",
|
||||
"validator-client",
|
||||
"vergen",
|
||||
"version-checker",
|
||||
]
|
||||
|
||||
@@ -3697,6 +3757,7 @@ dependencies = [
|
||||
"topology",
|
||||
"url",
|
||||
"validator-client",
|
||||
"vergen",
|
||||
"version-checker",
|
||||
]
|
||||
|
||||
@@ -3737,6 +3798,7 @@ dependencies = [
|
||||
"topology",
|
||||
"url",
|
||||
"validator-client",
|
||||
"vergen",
|
||||
"version-checker",
|
||||
]
|
||||
|
||||
@@ -3910,10 +3972,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "okapi"
|
||||
version = "0.6.0-alpha-1"
|
||||
version = "0.7.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb085e00daf8d75b9dbf0ffdb4738e69503e28898d9641fa8bdc6ad536c7bcf4"
|
||||
checksum = "ce66b6366e049880a35c378123fddb630b1a1a3c37fa1ca70caaf4a09f6e2893"
|
||||
dependencies = [
|
||||
"log",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -5150,10 +5213,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rocket_okapi"
|
||||
version = "0.7.0-alpha-1"
|
||||
version = "0.8.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b2f4f48fb070f9f6c56d5663df5fa8a514406207744f4abd84661bfb24efd7d"
|
||||
checksum = "0025aa04994af8cd8e1fcdd5a73579a395c941ae090ecb0a39b41cca7e237a20"
|
||||
dependencies = [
|
||||
"either",
|
||||
"log",
|
||||
"okapi",
|
||||
"rocket",
|
||||
"rocket_okapi_codegen",
|
||||
@@ -5164,9 +5229,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rocket_okapi_codegen"
|
||||
version = "0.7.0-alpha-1"
|
||||
version = "0.8.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ccf1550e1c806461a6b08e2ab64eb10701d41bf50bde59ab9aa3a57ab14d41"
|
||||
checksum = "dc114779fc27afb78179233e966f469e47fd7a98dc15181cff2574cdddb65612"
|
||||
dependencies = [
|
||||
"darling 0.13.0",
|
||||
"proc-macro2",
|
||||
@@ -7160,6 +7225,23 @@ version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "vergen"
|
||||
version = "5.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cf88d94e969e7956d924ba70741316796177fa0c79a2c9f4ab04998d96e966e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if 1.0.0",
|
||||
"chrono",
|
||||
"enum-iterator",
|
||||
"getset",
|
||||
"git2",
|
||||
"rustc_version 0.4.0",
|
||||
"rustversion",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version-checker"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -51,3 +51,6 @@ coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gate
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0" # for the "textsend" example
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use vergen::{vergen, Config};
|
||||
|
||||
fn main() {
|
||||
vergen(Config::default()).expect("failed to extract build metadata")
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{App, ArgMatches};
|
||||
use clap::{crate_version, App, ArgMatches};
|
||||
|
||||
pub mod client;
|
||||
pub mod commands;
|
||||
@@ -13,7 +13,8 @@ fn main() {
|
||||
println!("{}", banner());
|
||||
|
||||
let arg_matches = App::new("Nym Client")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.version(crate_version!())
|
||||
.long_version(&*long_version())
|
||||
.author("Nymtech")
|
||||
.about("Implementation of the Nym Client")
|
||||
.subcommand(commands::init::command_args())
|
||||
@@ -50,7 +51,38 @@ fn banner() -> String {
|
||||
(client - version {:})
|
||||
|
||||
"#,
|
||||
env!("CARGO_PKG_VERSION")
|
||||
crate_version!()
|
||||
)
|
||||
}
|
||||
|
||||
fn long_version() -> String {
|
||||
format!(
|
||||
r#"
|
||||
{:<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"),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -43,3 +43,6 @@ network-defaults = { path = "../../common/network-defaults" }
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use vergen::{vergen, Config};
|
||||
|
||||
fn main() {
|
||||
vergen(Config::default()).expect("failed to extract build metadata")
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{App, ArgMatches};
|
||||
use clap::{crate_version, App, ArgMatches};
|
||||
|
||||
pub mod client;
|
||||
mod commands;
|
||||
@@ -15,6 +15,7 @@ fn main() {
|
||||
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())
|
||||
@@ -50,7 +51,38 @@ fn banner() -> String {
|
||||
(socks5 proxy - version {:})
|
||||
|
||||
"#,
|
||||
env!("CARGO_PKG_VERSION")
|
||||
crate_version!()
|
||||
)
|
||||
}
|
||||
|
||||
fn long_version() -> String {
|
||||
format!(
|
||||
r#"
|
||||
{:<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"),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,41 +1,83 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"mocha": true
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"ecmaVersion": 2019,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"globals": {
|
||||
"Atomics": "readonly",
|
||||
"SharedArrayBuffer": "readonly"
|
||||
},
|
||||
"plugins": ["prettier", "mocha"],
|
||||
"extends": [
|
||||
"airbnb-base",
|
||||
"airbnb-typescript/base",
|
||||
"prettier"],
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"linebreak-style": "off",
|
||||
"quotes": [
|
||||
"error",
|
||||
"double",
|
||||
{
|
||||
"allowTemplateLiterals": true
|
||||
}
|
||||
],
|
||||
"keyword-spacing": [
|
||||
"prettier/prettier": "error",
|
||||
"import/prefer-default-export": "off",
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
"before": true
|
||||
"devDependencies": [
|
||||
"**/*.test.[jt]s",
|
||||
"**/*.spec.[jt]s"
|
||||
]
|
||||
}
|
||||
],
|
||||
"space-before-blocks": [
|
||||
"error"
|
||||
"import/extensions": [
|
||||
"error",
|
||||
"ignorePackages",
|
||||
{
|
||||
"ts": "never",
|
||||
"js": "never"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": "**/*.ts",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
],
|
||||
"quotes": "off",
|
||||
"@typescript-eslint/quotes": [
|
||||
2,
|
||||
"single",
|
||||
{
|
||||
"avoidEscape": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2
|
||||
}
|
||||
Generated
-3848
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nymproject/nym-validator-client",
|
||||
"version": "0.18.0",
|
||||
"version": "0.19.0",
|
||||
"description": "A TypeScript client for interacting with smart contracts in Nym validators",
|
||||
"repository": "https://github.com/nymtech/nym",
|
||||
"main": "./dist/index.js",
|
||||
@@ -9,7 +9,8 @@
|
||||
"build": "tsc",
|
||||
"test": "ts-mocha tests/**/*.test.ts",
|
||||
"coverage": "nyc npm test",
|
||||
"lint": "eslint \"**/*.ts\"",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"docs": "typedoc --out docs src/index.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
@@ -23,22 +24,33 @@
|
||||
"@types/chai": "^4.2.15",
|
||||
"@types/expect": "^24.3.0",
|
||||
"@types/mocha": "^8.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.14.0",
|
||||
"@typescript-eslint/parser": "^4.14.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.7.0",
|
||||
"@typescript-eslint/parser": "^5.7.0",
|
||||
"chai": "^4.2.0",
|
||||
"eslint": "^7.18.0",
|
||||
"eslint-config-airbnb": "^19.0.2",
|
||||
"eslint-config-airbnb-typescript": "^16.1.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-import-resolver-root-import": "^1.0.4",
|
||||
"eslint-plugin-import": "^2.25.3",
|
||||
"eslint-plugin-mocha": "^10.0.3",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"mocha": "^8.2.1",
|
||||
"moq.ts": "^7.2.0",
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^2.5.1",
|
||||
"ts-mocha": "^8.0.0",
|
||||
"typedoc": "^0.20.27",
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cosmjs/cosmwasm-stargate": "^0.27.0-rc2",
|
||||
"@cosmjs/crypto": "^0.27.0-rc2",
|
||||
"@cosmjs/math": "^0.27.0-rc2",
|
||||
"@cosmjs/proto-signing": "^0.27.0-rc2",
|
||||
"@cosmjs/stargate": "^0.27.0-rc2",
|
||||
"@cosmjs/tendermint-rpc": "^0.27.0-rc2",
|
||||
"axios": "^0.21.1",
|
||||
"@cosmjs/cosmwasm-stargate": "^0.25.5",
|
||||
"@cosmjs/stargate": "^0.25.5",
|
||||
"@cosmjs/math": "^0.25.5",
|
||||
"@cosmjs/proto-signing": "^0.25.5"
|
||||
"cosmjs-types": "^0.4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"mocha": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"linebreak-style": "off",
|
||||
"quotes": [
|
||||
"error",
|
||||
"double",
|
||||
{
|
||||
"allowTemplateLiterals": true
|
||||
}
|
||||
],
|
||||
"keyword-spacing": [
|
||||
"error",
|
||||
{
|
||||
"before": true
|
||||
}
|
||||
],
|
||||
"space-before-blocks": [
|
||||
"error"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
15.0.1
|
||||
@@ -1,59 +0,0 @@
|
||||
import {GatewayBond, PagedGatewayResponse} from "../types";
|
||||
import {INetClient} from "../net-client"
|
||||
import {IQueryClient} from "../query-client";
|
||||
import {VALIDATOR_API_GATEWAYS, VALIDATOR_API_PORT} from "../index";
|
||||
import axios from "axios";
|
||||
|
||||
|
||||
/**
|
||||
* There are serious limits in smart contract systems, but we need to keep track of
|
||||
* potentially thousands of nodes. GatewaysCache instances repeatedly make requests for
|
||||
* paged data about what gateways exist, and keep them locally in memory so that they're
|
||||
* available for querying.
|
||||
**/
|
||||
export default class GatewaysCache {
|
||||
gateways: GatewayBond[]
|
||||
client: INetClient | IQueryClient
|
||||
perPage: number
|
||||
|
||||
constructor(client: INetClient | IQueryClient, perPage: number) {
|
||||
this.client = client;
|
||||
this.gateways = [];
|
||||
this.perPage = perPage;
|
||||
}
|
||||
|
||||
/// Makes repeated requests to assemble a full list of gateways.
|
||||
/// Requests continue to be make as long as `shouldMakeAnotherRequest()`
|
||||
/// returns true.
|
||||
async refreshGateways(contractAddress: string): Promise<GatewayBond[]> {
|
||||
let newGateways: GatewayBond[] = [];
|
||||
let response: PagedGatewayResponse;
|
||||
let next: string | undefined = undefined;
|
||||
for (;;) {
|
||||
response = await this.client.getGateways(contractAddress, this.perPage, next);
|
||||
newGateways = newGateways.concat(response.nodes)
|
||||
next = response.start_next_after;
|
||||
// if `start_next_after` is not set, we're done
|
||||
if (!next) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.gateways = newGateways
|
||||
return newGateways;
|
||||
}
|
||||
|
||||
/// Makes requests to assemble a full list of gateways from validator-api
|
||||
async refreshValidatorAPIGateways(urls: string[]): Promise<GatewayBond[]> {
|
||||
for (const url of urls) {
|
||||
const validator_api_url = new URL(url);
|
||||
validator_api_url.port = VALIDATOR_API_PORT;
|
||||
validator_api_url.pathname += VALIDATOR_API_GATEWAYS;
|
||||
const response = await axios.get(validator_api_url.toString());
|
||||
if (response.status == 200) {
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
throw new Error("None of the provided validators seem to be alive")
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import {MixNodeBond, PagedMixnodeResponse} from "../types";
|
||||
import { INetClient } from "../net-client"
|
||||
import {IQueryClient} from "../query-client";
|
||||
import {VALIDATOR_API_MIXNODES, VALIDATOR_API_PORT} from "../index";
|
||||
import axios from "axios";
|
||||
|
||||
export { MixnodesCache };
|
||||
|
||||
/**
|
||||
* There are serious limits in smart contract systems, but we need to keep track of
|
||||
* potentially thousands of nodes. MixnodeCache instances repeatedly make requests for
|
||||
* paged data about what mixnodes exist, and keep them locally in memory so that they're
|
||||
* available for querying.
|
||||
* */
|
||||
export default class MixnodesCache {
|
||||
mixNodes: MixNodeBond[]
|
||||
client: INetClient | IQueryClient
|
||||
perPage: number
|
||||
|
||||
constructor(client: INetClient | IQueryClient, perPage: number) {
|
||||
this.client = client;
|
||||
this.mixNodes = [];
|
||||
this.perPage = perPage;
|
||||
}
|
||||
|
||||
/// Makes repeated requests to assemble a full list of nodes.
|
||||
/// Requests continue to be make as long as `shouldMakeAnotherRequest()`
|
||||
// returns true.
|
||||
async refreshMixNodes(contractAddress: string): Promise<MixNodeBond[]> {
|
||||
let newMixnodes: MixNodeBond[] = [];
|
||||
let response: PagedMixnodeResponse;
|
||||
let next: string | undefined = undefined;
|
||||
for (;;) {
|
||||
response = await this.client.getMixNodes(contractAddress, this.perPage, next);
|
||||
newMixnodes = newMixnodes.concat(response.nodes)
|
||||
next = response.start_next_after;
|
||||
// if `start_next_after` is not set, we're done
|
||||
if (!next) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.mixNodes = newMixnodes
|
||||
return this.mixNodes;
|
||||
}
|
||||
|
||||
/// Makes requests to assemble a full list of mixnodes from validator-api
|
||||
async refreshValidatorAPIMixNodes(urls: string[]): Promise<MixNodeBond[]> {
|
||||
for (const url of urls) {
|
||||
const validator_api_url = new URL(url);
|
||||
validator_api_url.port = VALIDATOR_API_PORT;
|
||||
validator_api_url.pathname += VALIDATOR_API_MIXNODES;
|
||||
const response = await axios.get(validator_api_url.toString());
|
||||
if (response.status == 200) {
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
throw new Error("None of the provided validators seem to be alive")
|
||||
}
|
||||
}
|
||||
@@ -1,73 +1,68 @@
|
||||
import { Decimal } from "@cosmjs/math";
|
||||
import { Coin } from ".";
|
||||
import { Decimal } from '@cosmjs/math';
|
||||
import { Coin } from '@cosmjs/stargate';
|
||||
|
||||
// NARROW NO-BREAK SPACE (U+202F)
|
||||
const thinSpace = "\u202F";
|
||||
const thinSpace = '\u202F';
|
||||
|
||||
export function printableCoin(coin?: Coin): string {
|
||||
if (!coin) {
|
||||
return "0";
|
||||
}
|
||||
if (coin.denom.startsWith("u")) {
|
||||
const ticker = coin.denom.slice(1).toUpperCase();
|
||||
return Decimal.fromAtomics(coin.amount, 6).toString() + thinSpace + ticker;
|
||||
} else {
|
||||
return coin.amount + thinSpace + coin.denom;
|
||||
}
|
||||
if (!coin) {
|
||||
return '0';
|
||||
}
|
||||
if (coin.denom.startsWith('u')) {
|
||||
const ticker = coin.denom.slice(1).toUpperCase();
|
||||
return Decimal.fromAtomics(coin.amount, 6).toString() + thinSpace + ticker;
|
||||
}
|
||||
return coin.amount + thinSpace + coin.denom;
|
||||
}
|
||||
|
||||
export function printableBalance(balance?: readonly Coin[]): string {
|
||||
if (!balance || balance.length === 0) return "–";
|
||||
return balance.map(printableCoin).join(", ");
|
||||
if (!balance || balance.length === 0) return '–';
|
||||
return balance.map(printableCoin).join(', ');
|
||||
}
|
||||
|
||||
// converts display amount, such as "12.0346" to its native token representation,
|
||||
// with 6 fractional digits. So in that case it would result in "12034600"
|
||||
// Basically does the same job as `displayAmountToNative` but without the requirement
|
||||
// of having the coinMap
|
||||
export function printableBalanceToNative(amountToDisplay: string): string {
|
||||
const decimalAmount = Decimal.fromUserInput(amountToDisplay, 6);
|
||||
return decimalAmount.atomics;
|
||||
export function printableBalanceToNative(amountToDisplay: string): string {
|
||||
const decimalAmount = Decimal.fromUserInput(amountToDisplay, 6);
|
||||
return decimalAmount.atomics;
|
||||
}
|
||||
|
||||
// reciprocal of `printableBalanceToNative`, takes, for example 10000000 and returns 10
|
||||
export function nativeToPrintable(nativeValue: string): string {
|
||||
return Decimal.fromAtomics(nativeValue, 6).toString()
|
||||
return Decimal.fromAtomics(nativeValue, 6).toString();
|
||||
}
|
||||
|
||||
export interface MappedCoin {
|
||||
readonly denom: string;
|
||||
readonly fractionalDigits: number;
|
||||
readonly denom: string;
|
||||
readonly fractionalDigits: number;
|
||||
}
|
||||
|
||||
export interface CoinMap {
|
||||
readonly [key: string]: MappedCoin;
|
||||
readonly [key: string]: MappedCoin;
|
||||
}
|
||||
|
||||
export function nativeCoinToDisplay(coin: Coin, coinMap: CoinMap): Coin {
|
||||
if (!coinMap) return coin;
|
||||
if (!coinMap) return coin;
|
||||
|
||||
const coinToDisplay = coinMap[coin.denom];
|
||||
if (!coinToDisplay) return coin;
|
||||
const coinToDisplay = coinMap[coin.denom];
|
||||
if (!coinToDisplay) return coin;
|
||||
|
||||
const amountToDisplay = Decimal.fromAtomics(coin.amount, coinToDisplay.fractionalDigits).toString();
|
||||
const amountToDisplay = Decimal.fromAtomics(coin.amount, coinToDisplay.fractionalDigits).toString();
|
||||
|
||||
return { denom: coinToDisplay.denom, amount: amountToDisplay };
|
||||
return { denom: coinToDisplay.denom, amount: amountToDisplay };
|
||||
}
|
||||
|
||||
// display amount is eg "12.0346", return is in native tokens
|
||||
// with 6 fractional digits, this would be eg. "12034600"
|
||||
export function displayAmountToNative(
|
||||
amountToDisplay: string,
|
||||
coinMap: CoinMap,
|
||||
nativeDenom: string,
|
||||
): string {
|
||||
const fractionalDigits = coinMap[nativeDenom]?.fractionalDigits;
|
||||
if (fractionalDigits) {
|
||||
// use https://github.com/CosmWasm/cosmjs/blob/v0.22.2/packages/math/src/decimal.ts
|
||||
const decimalAmount = Decimal.fromUserInput(amountToDisplay, fractionalDigits);
|
||||
return decimalAmount.atomics;
|
||||
}
|
||||
export function displayAmountToNative(amountToDisplay: string, coinMap: CoinMap, nativeDenom: string): string {
|
||||
const fractionalDigits = coinMap[nativeDenom]?.fractionalDigits;
|
||||
if (fractionalDigits) {
|
||||
// use https://github.com/CosmWasm/cosmjs/blob/v0.22.2/packages/math/src/decimal.ts
|
||||
const decimalAmount = Decimal.fromUserInput(amountToDisplay, fractionalDigits);
|
||||
return decimalAmount.atomics;
|
||||
}
|
||||
|
||||
return amountToDisplay;
|
||||
return amountToDisplay;
|
||||
}
|
||||
|
||||
+435
-622
File diff suppressed because it is too large
Load Diff
@@ -1,207 +0,0 @@
|
||||
import { SigningCosmWasmClient, SigningCosmWasmClientOptions } from "@cosmjs/cosmwasm-stargate";
|
||||
import {
|
||||
Delegation,
|
||||
GatewayOwnershipResponse,
|
||||
MixOwnershipResponse, PagedGatewayDelegationsResponse,
|
||||
PagedGatewayResponse, PagedMixDelegationsResponse,
|
||||
PagedMixnodeResponse,
|
||||
StateParams
|
||||
} from "./types";
|
||||
import { DirectSecp256k1HdWallet, EncodeObject } from "@cosmjs/proto-signing";
|
||||
import { Coin, StdFee } from "@cosmjs/stargate";
|
||||
import { BroadcastTxResponse } from "@cosmjs/stargate"
|
||||
import { nymGasLimits, nymGasPrice } from "./stargate-helper"
|
||||
import {
|
||||
ExecuteResult,
|
||||
InstantiateOptions,
|
||||
InstantiateResult,
|
||||
MigrateResult,
|
||||
UploadMeta,
|
||||
UploadResult
|
||||
} from "@cosmjs/cosmwasm-stargate";
|
||||
|
||||
export interface INetClient {
|
||||
clientAddress: string;
|
||||
|
||||
getBalance(address: string, denom: string): Promise<Coin | null>;
|
||||
|
||||
getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse>;
|
||||
|
||||
getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse>;
|
||||
|
||||
getMixDelegations(contractAddress: string, mixIdentity: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse>
|
||||
|
||||
getMixDelegation(contractAddress: string, mixIdentity: string, delegatorAddress: string): Promise<Delegation>
|
||||
|
||||
getGatewayDelegations(contractAddress: string, gatewayIdentity: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse>
|
||||
|
||||
getGatewayDelegation(contractAddress: string, gatewayIdentity: string, delegatorAddress: string): Promise<Delegation>
|
||||
|
||||
ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse>;
|
||||
|
||||
ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
|
||||
|
||||
getStateParams(contractAddress: string): Promise<StateParams>;
|
||||
|
||||
signAndBroadcast(signerAddress: string, messages: readonly EncodeObject[], fee: StdFee, memo?: string): Promise<BroadcastTxResponse>;
|
||||
|
||||
executeContract(senderAddress: string, contractAddress: string, handleMsg: Record<string, unknown>, memo?: string, transferAmount?: readonly Coin[]): Promise<ExecuteResult>;
|
||||
|
||||
instantiate(senderAddress: string, codeId: number, initMsg: Record<string, unknown>, label: string, options?: InstantiateOptions): Promise<InstantiateResult>;
|
||||
|
||||
sendTokens(senderAddress: string, recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise<BroadcastTxResponse>;
|
||||
|
||||
upload(senderAddress: string, wasmCode: Uint8Array, meta?: UploadMeta, memo?: string): Promise<UploadResult>;
|
||||
|
||||
changeValidator(newUrl: string): Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes care of network communication between this code and the validator.
|
||||
* Depends on `SigningCosmWasClient`, which signs all requests using keypairs
|
||||
* derived from on bech32 mnemonics.
|
||||
*
|
||||
* Wraps several methods from CosmWasmSigningClient so we can mock them for
|
||||
* unit testing.
|
||||
*/
|
||||
export default class NetClient implements INetClient {
|
||||
clientAddress: string;
|
||||
private cosmClient: SigningCosmWasmClient;
|
||||
|
||||
// helpers for changing validators without having to remake the wallet
|
||||
private readonly wallet: DirectSecp256k1HdWallet;
|
||||
private readonly signerOptions: SigningCosmWasmClientOptions;
|
||||
|
||||
private constructor(clientAddress: string, cosmClient: SigningCosmWasmClient, wallet: DirectSecp256k1HdWallet, signerOptions: SigningCosmWasmClientOptions) {
|
||||
this.clientAddress = clientAddress;
|
||||
this.cosmClient = cosmClient;
|
||||
this.wallet = wallet;
|
||||
this.signerOptions = signerOptions;
|
||||
}
|
||||
|
||||
public static async connect(wallet: DirectSecp256k1HdWallet, url: string, prefix: string): Promise<INetClient> {
|
||||
const [{ address }] = await wallet.getAccounts();
|
||||
const signerOptions: SigningCosmWasmClientOptions = {
|
||||
gasPrice: nymGasPrice(prefix),
|
||||
gasLimits: nymGasLimits,
|
||||
};
|
||||
const client = await SigningCosmWasmClient.connectWithSigner(url, wallet, signerOptions);
|
||||
return new NetClient(address, client, wallet, signerOptions);
|
||||
}
|
||||
|
||||
async changeValidator(url: string): Promise<void> {
|
||||
this.cosmClient = await SigningCosmWasmClient.connectWithSigner(url, this.wallet, this.signerOptions);
|
||||
}
|
||||
|
||||
public getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse> {
|
||||
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
|
||||
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_nodes: { limit } });
|
||||
} else {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_nodes: { limit, start_after } });
|
||||
}
|
||||
}
|
||||
|
||||
public getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse> {
|
||||
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
|
||||
return this.cosmClient.queryContractSmart(contractAddress, { get_gateways: { limit } });
|
||||
} else {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, { get_gateways: { limit, start_after } });
|
||||
}
|
||||
}
|
||||
|
||||
public getMixDelegations(contractAddress: string, mixIdentity: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse> {
|
||||
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
|
||||
return this.cosmClient.queryContractSmart(contractAddress, {
|
||||
get_mix_delegations: {
|
||||
mix_identity: mixIdentity,
|
||||
limit
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, {
|
||||
get_mix_delegations: {
|
||||
mix_identity: mixIdentity,
|
||||
limit,
|
||||
start_after
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public getMixDelegation(contractAddress: string, mixIdentity: string, delegatorAddress: string): Promise<Delegation> {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, {
|
||||
get_mix_delegation: {
|
||||
mix_identity: mixIdentity,
|
||||
address: delegatorAddress
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getGatewayDelegations(contractAddress: string, gatewayIdentity: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse> {
|
||||
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
|
||||
return this.cosmClient.queryContractSmart(contractAddress, {
|
||||
get_gateway_delegations: {
|
||||
gateway_identity: gatewayIdentity,
|
||||
limit
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, {
|
||||
get_gateway_delegations: {
|
||||
gateway_identity: gatewayIdentity,
|
||||
limit,
|
||||
start_after
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public getGatewayDelegation(contractAddress: string, gatewayIdentity: string, delegatorAddress: string): Promise<Delegation> {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, {
|
||||
get_gateway_delegation: {
|
||||
gateway_identity: gatewayIdentity,
|
||||
address: delegatorAddress
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse> {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, { owns_mixnode: { address } });
|
||||
}
|
||||
|
||||
public ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, { owns_gateway: { address } });
|
||||
}
|
||||
|
||||
public getBalance(address: string, denom: string): Promise<Coin | null> {
|
||||
return this.cosmClient.getBalance(address, denom);
|
||||
}
|
||||
|
||||
public getStateParams(contractAddress: string): Promise<StateParams> {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, { contract_settings_params: {} });
|
||||
}
|
||||
|
||||
public executeContract(senderAddress: string, contractAddress: string, handleMsg: Record<string, unknown>, memo?: string, transferAmount?: readonly Coin[]): Promise<ExecuteResult> {
|
||||
return this.cosmClient.execute(senderAddress, contractAddress, handleMsg, memo, transferAmount);
|
||||
}
|
||||
|
||||
public signAndBroadcast(signerAddress: string, messages: readonly EncodeObject[], fee: StdFee, memo?: string): Promise<BroadcastTxResponse> {
|
||||
return this.cosmClient.signAndBroadcast(signerAddress, messages, fee, memo)
|
||||
}
|
||||
|
||||
public sendTokens(senderAddress: string, recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise<BroadcastTxResponse> {
|
||||
return this.cosmClient.sendTokens(senderAddress, recipientAddress, transferAmount, memo);
|
||||
}
|
||||
|
||||
public upload(senderAddress: string, wasmCode: Uint8Array, meta?: UploadMeta, memo?: string): Promise<UploadResult> {
|
||||
return this.cosmClient.upload(senderAddress, wasmCode, meta, memo);
|
||||
}
|
||||
|
||||
public instantiate(senderAddress: string, codeId: number, initMsg: Record<string, unknown>, label: string, options?: InstantiateOptions): Promise<InstantiateResult> {
|
||||
return this.cosmClient.instantiate(senderAddress, codeId, initMsg, label, options);
|
||||
}
|
||||
|
||||
public migrate(senderAddress: string, contractAddress: string, codeId: number, migrateMsg: Record<string, unknown>, memo?: string): Promise<MigrateResult> {
|
||||
return this.cosmClient.migrate(senderAddress, contractAddress, codeId, migrateMsg, memo)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { JsonObject } from '@cosmjs/cosmwasm-stargate/build/queries';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { INymdQuery } from './query-client';
|
||||
import {
|
||||
ContractStateParams,
|
||||
Delegation,
|
||||
GatewayOwnershipResponse,
|
||||
LayerDistribution,
|
||||
MixnetContractVersion,
|
||||
MixOwnershipResponse,
|
||||
PagedAllDelegationsResponse,
|
||||
PagedDelegatorDelegationsResponse,
|
||||
PagedGatewayResponse,
|
||||
PagedMixDelegationsResponse,
|
||||
PagedMixnodeResponse,
|
||||
RewardingIntervalResponse,
|
||||
RewardingStatus,
|
||||
} from './types';
|
||||
|
||||
interface SmartContractQuery {
|
||||
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
|
||||
}
|
||||
|
||||
export default class NymdQuerier implements INymdQuery {
|
||||
client: SmartContractQuery;
|
||||
|
||||
constructor(client: SmartContractQuery) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
get_contract_version: {},
|
||||
});
|
||||
}
|
||||
|
||||
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
get_mix_nodes: {
|
||||
limit,
|
||||
start_after: startAfter,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
get_gateways: {
|
||||
limit,
|
||||
start_after: startAfter,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
owns_mixnode: {
|
||||
address,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
owns_gateway: {
|
||||
address,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
state_params: {},
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentRewardingInterval(mixnetContractAddress: string): Promise<RewardingIntervalResponse> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
current_rewarding_interval: {},
|
||||
});
|
||||
}
|
||||
|
||||
getAllNetworkDelegationsPaged(
|
||||
mixnetContractAddress: string,
|
||||
limit?: number,
|
||||
startAfter?: [string, string],
|
||||
): Promise<PagedAllDelegationsResponse> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
get_all_network_delegations: {
|
||||
start_after: startAfter,
|
||||
limit,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getMixNodeDelegationsPaged(
|
||||
mixnetContractAddress: string,
|
||||
mixIdentity: string,
|
||||
limit?: number,
|
||||
startAfter?: string,
|
||||
): Promise<PagedMixDelegationsResponse> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
get_mixnode_delegations: {
|
||||
mix_identity: mixIdentity,
|
||||
start_after: startAfter,
|
||||
limit,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getDelegatorDelegationsPaged(
|
||||
mixnetContractAddress: string,
|
||||
delegator: string,
|
||||
limit?: number,
|
||||
startAfter?: string,
|
||||
): Promise<PagedDelegatorDelegationsResponse> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
get_delegator_delegations: {
|
||||
delegator,
|
||||
start_after: startAfter,
|
||||
limit,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
get_delegation_details: {
|
||||
mix_identity: mixIdentity,
|
||||
delegator,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
layer_distribution: {},
|
||||
});
|
||||
}
|
||||
|
||||
getRewardPool(mixnetContractAddress: string): Promise<string> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
get_reward_pool: {},
|
||||
});
|
||||
}
|
||||
|
||||
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
get_circulating_supply: {},
|
||||
});
|
||||
}
|
||||
|
||||
getEpochRewardPercent(mixnetContractAddress: string): Promise<number> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
get_epoch_reward_percent: {},
|
||||
});
|
||||
}
|
||||
|
||||
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
get_sybil_resistance_percent: {},
|
||||
});
|
||||
}
|
||||
|
||||
getRewardingStatus(
|
||||
mixnetContractAddress: string,
|
||||
mixIdentity: string,
|
||||
rewardingIntervalNonce: number,
|
||||
): Promise<RewardingStatus> {
|
||||
return this.client.queryContractSmart(mixnetContractAddress, {
|
||||
get_rewarding_status: {
|
||||
mix_identity: mixIdentity,
|
||||
rewarding_interval_nonce: rewardingIntervalNonce,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,147 +1,218 @@
|
||||
import { Coin } from "@cosmjs/stargate";
|
||||
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
|
||||
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate';
|
||||
import { Tendermint34Client } from '@cosmjs/tendermint-rpc';
|
||||
import {
|
||||
Delegation,
|
||||
GatewayOwnershipResponse,
|
||||
MixOwnershipResponse, PagedGatewayDelegationsResponse,
|
||||
PagedGatewayResponse, PagedMixDelegationsResponse,
|
||||
PagedMixnodeResponse,
|
||||
ContractSettingsParams
|
||||
} from "./types";
|
||||
Account,
|
||||
Block,
|
||||
Coin,
|
||||
DeliverTxResponse,
|
||||
IndexedTx,
|
||||
SearchTxFilter,
|
||||
SearchTxQuery,
|
||||
SequenceResponse,
|
||||
} from '@cosmjs/stargate';
|
||||
import { JsonObject } from '@cosmjs/cosmwasm-stargate/build/queries';
|
||||
import { Code, CodeDetails, Contract, ContractCodeHistoryEntry } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import NymdQuerier from './nymd-querier';
|
||||
import {
|
||||
ContractStateParams,
|
||||
Delegation,
|
||||
GatewayBond,
|
||||
GatewayOwnershipResponse,
|
||||
LayerDistribution,
|
||||
MixnetContractVersion,
|
||||
MixNodeBond,
|
||||
MixOwnershipResponse,
|
||||
PagedAllDelegationsResponse,
|
||||
PagedDelegatorDelegationsResponse,
|
||||
PagedGatewayResponse,
|
||||
PagedMixDelegationsResponse,
|
||||
PagedMixnodeResponse,
|
||||
RewardingIntervalResponse,
|
||||
RewardingStatus,
|
||||
} from './types';
|
||||
import ValidatorApiQuerier, { IValidatorApiQuery } from './validator-api-querier';
|
||||
|
||||
export interface IQueryClient {
|
||||
getBalance(address: string, stakeDenom: string): Promise<Coin | null>;
|
||||
|
||||
getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse>;
|
||||
|
||||
getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse>;
|
||||
|
||||
getMixDelegations(contractAddress: string, mixIdentity: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse>
|
||||
|
||||
getMixDelegation(contractAddress: string, mixIdentity: string, delegatorAddress: string): Promise<Delegation>
|
||||
|
||||
getGatewayDelegations(contractAddress: string, gatewayIdentity: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse>
|
||||
|
||||
getGatewayDelegation(contractAddress: string, gatewayIdentity: string, delegatorAddress: string): Promise<Delegation>
|
||||
|
||||
ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse>;
|
||||
|
||||
ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
|
||||
|
||||
getStateParams(contractAddress: string): Promise<ContractSettingsParams>;
|
||||
|
||||
changeValidator(newUrl: string): Promise<void>
|
||||
export interface ICosmWasmQuery {
|
||||
// methods exposed by `CosmWasmClient`
|
||||
getChainId(): Promise<string>;
|
||||
getHeight(): Promise<number>;
|
||||
getAccount(searchAddress: string): Promise<Account | null>;
|
||||
getSequence(address: string): Promise<SequenceResponse>;
|
||||
getBlock(height?: number): Promise<Block>;
|
||||
getBalance(address: string, searchDenom: string): Promise<Coin>;
|
||||
getTx(id: string): Promise<IndexedTx | null>;
|
||||
searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise<readonly IndexedTx[]>;
|
||||
disconnect(): void;
|
||||
broadcastTx(tx: Uint8Array, timeoutMs?: number, pollIntervalMs?: number): Promise<DeliverTxResponse>;
|
||||
getCodes(): Promise<readonly Code[]>;
|
||||
getCodeDetails(codeId: number): Promise<CodeDetails>;
|
||||
getContracts(codeId: number): Promise<readonly string[]>;
|
||||
getContract(address: string): Promise<Contract>;
|
||||
getContractCodeHistory(address: string): Promise<readonly ContractCodeHistoryEntry[]>;
|
||||
queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null>;
|
||||
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes care of network communication between this code and the validator.
|
||||
* Depends on `SigningCosmWasClient`, which signs all requests using keypairs
|
||||
* derived from on bech32 mnemonics.
|
||||
*
|
||||
* Wraps several methods from CosmWasmSigningClient so we can mock them for
|
||||
* unit testing.
|
||||
*/
|
||||
export default class QueryClient implements IQueryClient {
|
||||
private cosmClient: CosmWasmClient;
|
||||
export interface INymdQuery {
|
||||
// nym-specific implemented inside NymQuerier
|
||||
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion>;
|
||||
|
||||
private constructor(cosmClient: CosmWasmClient) {
|
||||
this.cosmClient = cosmClient;
|
||||
}
|
||||
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse>;
|
||||
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse>;
|
||||
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse>;
|
||||
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
|
||||
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams>;
|
||||
getCurrentRewardingInterval(mixnetContractAddress: string): Promise<RewardingIntervalResponse>;
|
||||
|
||||
public static async connect(url: string): Promise<IQueryClient> {
|
||||
const client = await CosmWasmClient.connect(url)
|
||||
return new QueryClient(client)
|
||||
}
|
||||
getAllNetworkDelegationsPaged(
|
||||
mixnetContractAddress: string,
|
||||
limit?: number,
|
||||
startAfter?: [string, string],
|
||||
): Promise<PagedAllDelegationsResponse>;
|
||||
getMixNodeDelegationsPaged(
|
||||
mixnetContractAddress: string,
|
||||
mixIdentity: string,
|
||||
limit?: number,
|
||||
startAfter?: string,
|
||||
): Promise<PagedMixDelegationsResponse>;
|
||||
getDelegatorDelegationsPaged(
|
||||
mixnetContractAddress: string,
|
||||
delegator: string,
|
||||
limit?: number,
|
||||
startAfter?: string,
|
||||
): Promise<PagedDelegatorDelegationsResponse>;
|
||||
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation>;
|
||||
|
||||
async changeValidator(url: string): Promise<void> {
|
||||
this.cosmClient = await CosmWasmClient.connect(url)
|
||||
}
|
||||
|
||||
public getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse> {
|
||||
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
|
||||
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_nodes: { limit } });
|
||||
} else {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, { get_mix_nodes: { limit, start_after } });
|
||||
}
|
||||
}
|
||||
|
||||
public getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse> {
|
||||
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
|
||||
return this.cosmClient.queryContractSmart(contractAddress, { get_gateways: { limit } });
|
||||
} else {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, { get_gateways: { limit, start_after } });
|
||||
}
|
||||
}
|
||||
|
||||
public getMixDelegations(contractAddress: string, mixIdentity: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse> {
|
||||
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
|
||||
return this.cosmClient.queryContractSmart(contractAddress, {
|
||||
get_mix_delegations: {
|
||||
mix_identity: mixIdentity,
|
||||
limit
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, {
|
||||
get_mix_delegations: {
|
||||
mix_identity: mixIdentity,
|
||||
limit,
|
||||
start_after
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public getMixDelegation(contractAddress: string, mixIdentity: string, delegatorAddress: string): Promise<Delegation> {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, {
|
||||
get_mix_delegation: {
|
||||
mix_identity: mixIdentity,
|
||||
address: delegatorAddress
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getGatewayDelegations(contractAddress: string, gatewayIdentity: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse> {
|
||||
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
|
||||
return this.cosmClient.queryContractSmart(contractAddress, {
|
||||
get_gateway_delegations: {
|
||||
gateway_identity: gatewayIdentity,
|
||||
limit
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, {
|
||||
get_gateway_delegations: {
|
||||
gateway_identity: gatewayIdentity,
|
||||
limit,
|
||||
start_after
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public getGatewayDelegation(contractAddress: string, gatewayIdentity: string, delegatorAddress: string): Promise<Delegation> {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, {
|
||||
get_gateway_delegation: {
|
||||
gateway_identity: gatewayIdentity,
|
||||
address: delegatorAddress
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse> {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, { owns_mixnode: { address } });
|
||||
}
|
||||
|
||||
public ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, { owns_gateway: { address } });
|
||||
}
|
||||
|
||||
public getBalance(address: string, stakeDenom: string): Promise<Coin | null> {
|
||||
return this.cosmClient.getBalance(address, stakeDenom);
|
||||
}
|
||||
|
||||
public getStateParams(contractAddress: string): Promise<ContractSettingsParams> {
|
||||
return this.cosmClient.queryContractSmart(contractAddress, { contract_settings_params: {} });
|
||||
}
|
||||
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution>;
|
||||
getRewardPool(mixnetContractAddress: string): Promise<string>;
|
||||
getCirculatingSupply(mixnetContractAddress: string): Promise<string>;
|
||||
getEpochRewardPercent(mixnetContractAddress: string): Promise<number>;
|
||||
getSybilResistancePercent(mixnetContractAddress: string): Promise<number>;
|
||||
getRewardingStatus(
|
||||
mixnetContractAddress: string,
|
||||
mixIdentity: string,
|
||||
rewardingIntervalNonce: number,
|
||||
): Promise<RewardingStatus>;
|
||||
}
|
||||
|
||||
export interface IQueryClient extends ICosmWasmQuery, INymdQuery, IValidatorApiQuery {}
|
||||
|
||||
export default class QueryClient extends CosmWasmClient implements IQueryClient {
|
||||
private nymdQuerier: NymdQuerier;
|
||||
|
||||
private validatorApiQuerier: ValidatorApiQuerier;
|
||||
|
||||
private constructor(tmClient: Tendermint34Client, validatorApiUrl: string) {
|
||||
super(tmClient);
|
||||
this.nymdQuerier = new NymdQuerier(this);
|
||||
this.validatorApiQuerier = new ValidatorApiQuerier(validatorApiUrl);
|
||||
}
|
||||
|
||||
public static async connectWithNym(nymdUrl: string, validatorApiUrl: string): Promise<QueryClient> {
|
||||
const tmClient = await Tendermint34Client.connect(nymdUrl);
|
||||
return new QueryClient(tmClient, validatorApiUrl);
|
||||
}
|
||||
|
||||
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion> {
|
||||
return this.nymdQuerier.getContractVersion(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
|
||||
return this.nymdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter);
|
||||
}
|
||||
|
||||
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
|
||||
return this.nymdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter);
|
||||
}
|
||||
|
||||
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
|
||||
return this.nymdQuerier.ownsMixNode(mixnetContractAddress, address);
|
||||
}
|
||||
|
||||
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
|
||||
return this.nymdQuerier.ownsGateway(mixnetContractAddress, address);
|
||||
}
|
||||
|
||||
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
|
||||
return this.nymdQuerier.getStateParams(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getCurrentRewardingInterval(mixnetContractAddress: string): Promise<RewardingIntervalResponse> {
|
||||
return this.nymdQuerier.getCurrentRewardingInterval(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getAllNetworkDelegationsPaged(
|
||||
mixnetContractAddress: string,
|
||||
limit?: number,
|
||||
startAfter?: [string, string],
|
||||
): Promise<PagedAllDelegationsResponse> {
|
||||
return this.nymdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter);
|
||||
}
|
||||
|
||||
getMixNodeDelegationsPaged(
|
||||
mixnetContractAddress: string,
|
||||
mixIdentity: string,
|
||||
limit?: number,
|
||||
startAfter?: string,
|
||||
): Promise<PagedMixDelegationsResponse> {
|
||||
return this.nymdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter);
|
||||
}
|
||||
|
||||
getDelegatorDelegationsPaged(
|
||||
mixnetContractAddress: string,
|
||||
delegator: string,
|
||||
limit?: number,
|
||||
startAfter?: string,
|
||||
): Promise<PagedDelegatorDelegationsResponse> {
|
||||
return this.nymdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter);
|
||||
}
|
||||
|
||||
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
|
||||
return this.nymdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator);
|
||||
}
|
||||
|
||||
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
|
||||
return this.nymdQuerier.getLayerDistribution(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getRewardPool(mixnetContractAddress: string): Promise<string> {
|
||||
return this.nymdQuerier.getRewardPool(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
|
||||
return this.nymdQuerier.getCirculatingSupply(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getEpochRewardPercent(mixnetContractAddress: string): Promise<number> {
|
||||
return this.nymdQuerier.getEpochRewardPercent(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
|
||||
return this.nymdQuerier.getSybilResistancePercent(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getRewardingStatus(
|
||||
mixnetContractAddress: string,
|
||||
mixIdentity: string,
|
||||
rewardingIntervalNonce: number,
|
||||
): Promise<RewardingStatus> {
|
||||
return this.nymdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce);
|
||||
}
|
||||
|
||||
getCachedGateways(): Promise<GatewayBond[]> {
|
||||
return this.validatorApiQuerier.getCachedGateways();
|
||||
}
|
||||
|
||||
getCachedMixnodes(): Promise<MixNodeBond[]> {
|
||||
return this.validatorApiQuerier.getCachedMixnodes();
|
||||
}
|
||||
|
||||
getActiveMixnodes(): Promise<MixNodeBond[]> {
|
||||
return this.validatorApiQuerier.getActiveMixnodes();
|
||||
}
|
||||
|
||||
getRewardedMixnodes(): Promise<MixNodeBond[]> {
|
||||
return this.validatorApiQuerier.getRewardedMixnodes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,467 @@
|
||||
import {
|
||||
ExecuteResult,
|
||||
InstantiateOptions,
|
||||
InstantiateResult,
|
||||
MigrateResult,
|
||||
SigningCosmWasmClient,
|
||||
SigningCosmWasmClientOptions,
|
||||
UploadResult,
|
||||
} from '@cosmjs/cosmwasm-stargate';
|
||||
import { DirectSecp256k1HdWallet, EncodeObject } from '@cosmjs/proto-signing';
|
||||
import { Coin, DeliverTxResponse, SignerData, StdFee } from '@cosmjs/stargate';
|
||||
import { Tendermint34Client } from '@cosmjs/tendermint-rpc';
|
||||
import { ChangeAdminResult } from '@cosmjs/cosmwasm-stargate/build/signingcosmwasmclient';
|
||||
import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
|
||||
import { nymGasPrice } from './stargate-helper';
|
||||
import { IQueryClient } from './query-client';
|
||||
import NymdQuerier from './nymd-querier';
|
||||
import {
|
||||
ContractStateParams,
|
||||
Delegation,
|
||||
Gateway,
|
||||
GatewayBond,
|
||||
GatewayOwnershipResponse,
|
||||
LayerDistribution,
|
||||
MixnetContractVersion,
|
||||
MixNode,
|
||||
MixNodeBond,
|
||||
MixOwnershipResponse,
|
||||
PagedAllDelegationsResponse,
|
||||
PagedDelegatorDelegationsResponse,
|
||||
PagedGatewayResponse,
|
||||
PagedMixDelegationsResponse,
|
||||
PagedMixnodeResponse,
|
||||
RewardingIntervalResponse,
|
||||
RewardingStatus,
|
||||
} from './types';
|
||||
import ValidatorApiQuerier from './validator-api-querier';
|
||||
|
||||
// methods exposed by `SigningCosmWasmClient`
|
||||
export interface ICosmWasmSigning {
|
||||
simulate(signerAddress: string, messages: readonly EncodeObject[], memo: string | undefined): Promise<number>;
|
||||
|
||||
upload(
|
||||
senderAddress: string,
|
||||
wasmCode: Uint8Array,
|
||||
fee: StdFee | 'auto' | number,
|
||||
memo?: string,
|
||||
): Promise<UploadResult>;
|
||||
|
||||
instantiate(
|
||||
senderAddress: string,
|
||||
codeId: number,
|
||||
msg: Record<string, unknown>,
|
||||
label: string,
|
||||
fee: StdFee | 'auto' | number,
|
||||
options?: InstantiateOptions,
|
||||
): Promise<InstantiateResult>;
|
||||
|
||||
updateAdmin(
|
||||
senderAddress: string,
|
||||
contractAddress: string,
|
||||
newAdmin: string,
|
||||
fee: StdFee | 'auto' | number,
|
||||
memo?: string,
|
||||
): Promise<ChangeAdminResult>;
|
||||
|
||||
clearAdmin(
|
||||
senderAddress: string,
|
||||
contractAddress: string,
|
||||
fee: StdFee | 'auto' | number,
|
||||
memo?: string,
|
||||
): Promise<ChangeAdminResult>;
|
||||
|
||||
migrate(
|
||||
senderAddress: string,
|
||||
contractAddress: string,
|
||||
codeId: number,
|
||||
migrateMsg: Record<string, unknown>,
|
||||
fee: StdFee | 'auto' | number,
|
||||
memo?: string,
|
||||
): Promise<MigrateResult>;
|
||||
|
||||
execute(
|
||||
senderAddress: string,
|
||||
contractAddress: string,
|
||||
msg: Record<string, unknown>,
|
||||
fee: StdFee | 'auto' | number,
|
||||
memo?: string,
|
||||
funds?: readonly Coin[],
|
||||
): Promise<ExecuteResult>;
|
||||
|
||||
sendTokens(
|
||||
senderAddress: string,
|
||||
recipientAddress: string,
|
||||
amount: readonly Coin[],
|
||||
fee: StdFee | 'auto' | number,
|
||||
memo?: string,
|
||||
): Promise<DeliverTxResponse>;
|
||||
|
||||
delegateTokens(
|
||||
delegatorAddress: string,
|
||||
validatorAddress: string,
|
||||
amount: Coin,
|
||||
fee: StdFee | 'auto' | number,
|
||||
memo?: string,
|
||||
): Promise<DeliverTxResponse>;
|
||||
|
||||
undelegateTokens(
|
||||
delegatorAddress: string,
|
||||
validatorAddress: string,
|
||||
amount: Coin,
|
||||
fee: StdFee | 'auto' | number,
|
||||
memo?: string,
|
||||
): Promise<DeliverTxResponse>;
|
||||
|
||||
withdrawRewards(
|
||||
delegatorAddress: string,
|
||||
validatorAddress: string,
|
||||
fee: StdFee | 'auto' | number,
|
||||
memo?: string,
|
||||
): Promise<DeliverTxResponse>;
|
||||
|
||||
signAndBroadcast(
|
||||
signerAddress: string,
|
||||
messages: readonly EncodeObject[],
|
||||
fee: StdFee | 'auto' | number,
|
||||
memo?: string,
|
||||
): Promise<DeliverTxResponse>;
|
||||
|
||||
sign(
|
||||
signerAddress: string,
|
||||
messages: readonly EncodeObject[],
|
||||
fee: StdFee,
|
||||
memo: string,
|
||||
explicitSignerData?: SignerData,
|
||||
): Promise<TxRaw>;
|
||||
}
|
||||
|
||||
export interface INymSigning {
|
||||
clientAddress: string;
|
||||
}
|
||||
|
||||
export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSigning {
|
||||
bondMixNode(
|
||||
mixnetContractAddress: string,
|
||||
mixNode: MixNode,
|
||||
ownerSignature: string,
|
||||
pledge: Coin,
|
||||
fee?: StdFee | 'auto' | number,
|
||||
memo?: string,
|
||||
): Promise<ExecuteResult>;
|
||||
|
||||
unbondMixNode(mixnetContractAddress: string, fee?: StdFee | 'auto' | number, memo?: string): Promise<ExecuteResult>;
|
||||
|
||||
bondGateway(
|
||||
mixnetContractAddress: string,
|
||||
gateway: Gateway,
|
||||
ownerSignature: string,
|
||||
pledge: Coin,
|
||||
fee?: StdFee | 'auto' | number,
|
||||
memo?: string,
|
||||
): Promise<ExecuteResult>;
|
||||
|
||||
unbondGateway(mixnetContractAddress: string, fee?: StdFee | 'auto' | number, memo?: string): Promise<ExecuteResult>;
|
||||
|
||||
delegateToMixNode(
|
||||
mixnetContractAddress: string,
|
||||
mixIdentity: string,
|
||||
amount: Coin,
|
||||
fee?: StdFee | 'auto' | number,
|
||||
memo?: string,
|
||||
): Promise<ExecuteResult>;
|
||||
|
||||
undelegateFromMixNode(
|
||||
mixnetContractAddress: string,
|
||||
mixIdentity: string,
|
||||
fee?: StdFee | 'auto' | number,
|
||||
memo?: string,
|
||||
): Promise<ExecuteResult>;
|
||||
|
||||
updateContractStateParams(
|
||||
mixnetContractAddress: string,
|
||||
newParams: ContractStateParams,
|
||||
fee?: StdFee | 'auto' | number,
|
||||
memo?: string,
|
||||
): Promise<ExecuteResult>;
|
||||
|
||||
// I don't see any point in exposing rewarding / vesting-related (INSIDE mixnet contract, like "BondMixnodeOnBehalf")
|
||||
// functionalities in our typescript client. However, if for some reason, we find we need them
|
||||
// they're rather trivial to add.
|
||||
}
|
||||
|
||||
export default class SigningClient extends SigningCosmWasmClient implements ISigningClient {
|
||||
private nymdQuerier: NymdQuerier;
|
||||
|
||||
private validatorApiQuerier: ValidatorApiQuerier;
|
||||
|
||||
clientAddress: string;
|
||||
|
||||
private constructor(
|
||||
clientAddress: string,
|
||||
validatorApiUrl: string,
|
||||
tmClient: Tendermint34Client,
|
||||
wallet: DirectSecp256k1HdWallet,
|
||||
signerOptions: SigningCosmWasmClientOptions,
|
||||
) {
|
||||
super(tmClient, wallet, signerOptions);
|
||||
this.clientAddress = clientAddress;
|
||||
this.nymdQuerier = new NymdQuerier(this);
|
||||
this.validatorApiQuerier = new ValidatorApiQuerier(validatorApiUrl);
|
||||
}
|
||||
|
||||
public static async connectWithNymSigner(
|
||||
wallet: DirectSecp256k1HdWallet,
|
||||
nymdUrl: string,
|
||||
validatorApiUrl: string,
|
||||
prefix: string,
|
||||
): Promise<SigningClient> {
|
||||
const [{ address }] = await wallet.getAccounts();
|
||||
const signerOptions: SigningCosmWasmClientOptions = {
|
||||
gasPrice: nymGasPrice(prefix),
|
||||
};
|
||||
const tmClient = await Tendermint34Client.connect(nymdUrl);
|
||||
return new SigningClient(address, validatorApiUrl, tmClient, wallet, signerOptions);
|
||||
}
|
||||
|
||||
// query related:
|
||||
|
||||
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion> {
|
||||
return this.nymdQuerier.getContractVersion(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
|
||||
return this.nymdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter);
|
||||
}
|
||||
|
||||
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
|
||||
return this.nymdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter);
|
||||
}
|
||||
|
||||
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
|
||||
return this.nymdQuerier.ownsMixNode(mixnetContractAddress, address);
|
||||
}
|
||||
|
||||
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
|
||||
return this.nymdQuerier.ownsGateway(mixnetContractAddress, address);
|
||||
}
|
||||
|
||||
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
|
||||
return this.nymdQuerier.getStateParams(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getCurrentRewardingInterval(mixnetContractAddress: string): Promise<RewardingIntervalResponse> {
|
||||
return this.nymdQuerier.getCurrentRewardingInterval(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getAllNetworkDelegationsPaged(
|
||||
mixnetContractAddress: string,
|
||||
limit?: number,
|
||||
startAfter?: [string, string],
|
||||
): Promise<PagedAllDelegationsResponse> {
|
||||
return this.nymdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter);
|
||||
}
|
||||
|
||||
getMixNodeDelegationsPaged(
|
||||
mixnetContractAddress: string,
|
||||
mixIdentity: string,
|
||||
limit?: number,
|
||||
startAfter?: string,
|
||||
): Promise<PagedMixDelegationsResponse> {
|
||||
return this.nymdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter);
|
||||
}
|
||||
|
||||
getDelegatorDelegationsPaged(
|
||||
mixnetContractAddress: string,
|
||||
delegator: string,
|
||||
limit?: number,
|
||||
startAfter?: string,
|
||||
): Promise<PagedDelegatorDelegationsResponse> {
|
||||
return this.nymdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter);
|
||||
}
|
||||
|
||||
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
|
||||
return this.nymdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator);
|
||||
}
|
||||
|
||||
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
|
||||
return this.nymdQuerier.getLayerDistribution(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getRewardPool(mixnetContractAddress: string): Promise<string> {
|
||||
return this.nymdQuerier.getRewardPool(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
|
||||
return this.nymdQuerier.getCirculatingSupply(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getEpochRewardPercent(mixnetContractAddress: string): Promise<number> {
|
||||
return this.nymdQuerier.getEpochRewardPercent(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
|
||||
return this.nymdQuerier.getSybilResistancePercent(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getRewardingStatus(
|
||||
mixnetContractAddress: string,
|
||||
mixIdentity: string,
|
||||
rewardingIntervalNonce: number,
|
||||
): Promise<RewardingStatus> {
|
||||
return this.nymdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce);
|
||||
}
|
||||
|
||||
getCachedGateways(): Promise<GatewayBond[]> {
|
||||
return this.validatorApiQuerier.getCachedGateways();
|
||||
}
|
||||
|
||||
getCachedMixnodes(): Promise<MixNodeBond[]> {
|
||||
return this.validatorApiQuerier.getCachedMixnodes();
|
||||
}
|
||||
|
||||
getActiveMixnodes(): Promise<MixNodeBond[]> {
|
||||
return this.validatorApiQuerier.getActiveMixnodes();
|
||||
}
|
||||
|
||||
getRewardedMixnodes(): Promise<MixNodeBond[]> {
|
||||
return this.validatorApiQuerier.getRewardedMixnodes();
|
||||
}
|
||||
|
||||
// signing related:
|
||||
|
||||
bondMixNode(
|
||||
mixnetContractAddress: string,
|
||||
mixNode: MixNode,
|
||||
ownerSignature: string,
|
||||
pledge: Coin,
|
||||
fee: StdFee | 'auto' | number = 'auto',
|
||||
memo = 'Default MixNode Bonding from Typescript',
|
||||
): Promise<ExecuteResult> {
|
||||
return this.execute(
|
||||
this.clientAddress,
|
||||
mixnetContractAddress,
|
||||
{
|
||||
bond_mixnode: {
|
||||
mix_node: mixNode,
|
||||
owner_signature: ownerSignature,
|
||||
},
|
||||
},
|
||||
fee,
|
||||
memo,
|
||||
[pledge],
|
||||
);
|
||||
}
|
||||
|
||||
unbondMixNode(
|
||||
mixnetContractAddress: string,
|
||||
fee: StdFee | 'auto' | number = 'auto',
|
||||
memo = 'Default MixNode Unbonding from Typescript',
|
||||
): Promise<ExecuteResult> {
|
||||
return this.execute(
|
||||
this.clientAddress,
|
||||
mixnetContractAddress,
|
||||
{
|
||||
unbond_mixnode: {},
|
||||
},
|
||||
fee,
|
||||
memo,
|
||||
);
|
||||
}
|
||||
|
||||
bondGateway(
|
||||
mixnetContractAddress: string,
|
||||
gateway: Gateway,
|
||||
ownerSignature: string,
|
||||
pledge: Coin,
|
||||
fee: StdFee | 'auto' | number = 'auto',
|
||||
memo = 'Default Gateway Bonding from Typescript',
|
||||
): Promise<ExecuteResult> {
|
||||
return this.execute(
|
||||
this.clientAddress,
|
||||
mixnetContractAddress,
|
||||
{
|
||||
bond_gateway: {
|
||||
gateway,
|
||||
owner_signature: ownerSignature,
|
||||
},
|
||||
},
|
||||
fee,
|
||||
memo,
|
||||
[pledge],
|
||||
);
|
||||
}
|
||||
|
||||
unbondGateway(
|
||||
mixnetContractAddress: string,
|
||||
fee: StdFee | 'auto' | number = 'auto',
|
||||
memo = 'Default Gateway Unbonding from Typescript',
|
||||
): Promise<ExecuteResult> {
|
||||
return this.execute(
|
||||
this.clientAddress,
|
||||
mixnetContractAddress,
|
||||
{
|
||||
unbond_gateway: {},
|
||||
},
|
||||
fee,
|
||||
memo,
|
||||
);
|
||||
}
|
||||
|
||||
delegateToMixNode(
|
||||
mixnetContractAddress: string,
|
||||
mixIdentity: string,
|
||||
amount: Coin,
|
||||
fee: StdFee | 'auto' | number = 'auto',
|
||||
memo = 'Default MixNode Delegation from Typescript',
|
||||
): Promise<ExecuteResult> {
|
||||
return this.execute(
|
||||
this.clientAddress,
|
||||
mixnetContractAddress,
|
||||
{
|
||||
delegate_to_mixnode: {
|
||||
mix_identity: mixIdentity,
|
||||
},
|
||||
},
|
||||
fee,
|
||||
memo,
|
||||
[amount],
|
||||
);
|
||||
}
|
||||
|
||||
undelegateFromMixNode(
|
||||
mixnetContractAddress: string,
|
||||
mixIdentity: string,
|
||||
fee: StdFee | 'auto' | number = 'auto',
|
||||
memo = 'Default MixNode Undelegation from Typescript',
|
||||
): Promise<ExecuteResult> {
|
||||
return this.execute(
|
||||
this.clientAddress,
|
||||
mixnetContractAddress,
|
||||
{
|
||||
undelegate_from_mixnode: {
|
||||
mix_identity: mixIdentity,
|
||||
},
|
||||
},
|
||||
fee,
|
||||
memo,
|
||||
);
|
||||
}
|
||||
|
||||
updateContractStateParams(
|
||||
mixnetContractAddress: string,
|
||||
newParams: ContractStateParams,
|
||||
fee: StdFee | 'auto' | number = 'auto',
|
||||
memo = 'Default Contract State Params Update from Typescript',
|
||||
): Promise<ExecuteResult> {
|
||||
return this.execute(
|
||||
this.clientAddress,
|
||||
mixnetContractAddress,
|
||||
{
|
||||
update_contract_state_params: newParams,
|
||||
},
|
||||
fee,
|
||||
memo,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,14 @@
|
||||
import axios from "axios";
|
||||
import { GasLimits, GasPrice } from "@cosmjs/stargate";
|
||||
import { CosmWasmFeeTable, defaultGasLimits } from "@cosmjs/cosmwasm-stargate";
|
||||
|
||||
export const nymGasLimits: GasLimits<CosmWasmFeeTable> = {
|
||||
...defaultGasLimits,
|
||||
upload: 2_500_000,
|
||||
init: 500_000,
|
||||
migrate: 200_000,
|
||||
exec: 250_000,
|
||||
send: 80_000,
|
||||
changeAdmin: 80_000,
|
||||
};
|
||||
import axios from 'axios';
|
||||
import { GasPrice } from '@cosmjs/stargate';
|
||||
|
||||
export function nymGasPrice(prefix: string): GasPrice {
|
||||
return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here.
|
||||
return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here.
|
||||
}
|
||||
|
||||
export const downloadWasm = async (url: string): Promise<Uint8Array> => {
|
||||
const r = await axios.get(url, {responseType: "arraybuffer"});
|
||||
if (r.status !== 200) {
|
||||
throw new Error(`Download error: ${r.status}`);
|
||||
}
|
||||
return r.data;
|
||||
const r = await axios.get(url, { responseType: 'arraybuffer' });
|
||||
if (r.status !== 200) {
|
||||
throw new Error(`Download error: ${r.status}`);
|
||||
}
|
||||
return r.data;
|
||||
};
|
||||
|
||||
|
||||
+133
-96
@@ -1,125 +1,162 @@
|
||||
import { Coin } from "@cosmjs/stargate";
|
||||
import { Coin } from '@cosmjs/stargate';
|
||||
|
||||
// TODO: ideally we'd have re-exported those using that fancy crate that builds ts types from rust
|
||||
|
||||
export type MixnetContractVersion = {
|
||||
build_timestamp: string;
|
||||
build_version: string;
|
||||
commit_sha: string;
|
||||
commit_timestamp: string;
|
||||
commit_branch: string;
|
||||
rustc_version: string;
|
||||
};
|
||||
|
||||
/// One page of a possible multi-page set of mixnodes. The paging interface is quite
|
||||
/// inconvenient, as we don't have the two pieces of information we need to know
|
||||
/// in order to do paging nicely (namely `currentPage` and `totalPages` parameters).
|
||||
///
|
||||
/// Instead, we have only `start_next_page_after`, i.e. the key of the last record
|
||||
/// on this page. In order to get the *next* page, CosmWasm looks at that value,
|
||||
/// finds the next record, and builds the next page starting there. This happens
|
||||
/// **in series** rather than **in parallel** (!).
|
||||
///
|
||||
/// So we have some consistency problems:
|
||||
///
|
||||
/// * we can't make requests at a given block height, so the result set
|
||||
/// which we assemble over time may change while requests are being made.
|
||||
/// * at some point we will make a request for a `start_next_page_after` key
|
||||
/// which has just been deleted from the database.
|
||||
///
|
||||
/// TODO: more robust error handling on the "deleted key" case.
|
||||
export type PagedMixnodeResponse = {
|
||||
nodes: MixNodeBond[],
|
||||
per_page: number, // TODO: camelCase
|
||||
start_next_after: string, // TODO: camelCase
|
||||
}
|
||||
nodes: MixNodeBond[];
|
||||
per_page: number;
|
||||
start_next_after?: string;
|
||||
};
|
||||
|
||||
// a temporary way of achieving the same paging behaviour for the gateways
|
||||
// the same points made for `PagedResponse` stand here.
|
||||
export type PagedGatewayResponse = {
|
||||
nodes: GatewayBond[],
|
||||
per_page: number, // TODO: camelCase
|
||||
start_next_after: string, // TODO: camelCase
|
||||
}
|
||||
nodes: GatewayBond[];
|
||||
per_page: number;
|
||||
start_next_after?: string;
|
||||
};
|
||||
|
||||
export type MixOwnershipResponse = {
|
||||
address: string,
|
||||
has_node: boolean,
|
||||
}
|
||||
address: string;
|
||||
mixnode?: MixNodeBond;
|
||||
};
|
||||
|
||||
export type GatewayOwnershipResponse = {
|
||||
address: string,
|
||||
has_gateway: boolean,
|
||||
}
|
||||
address: string;
|
||||
gateway?: GatewayBond;
|
||||
};
|
||||
|
||||
export type ContractSettingsParams = {
|
||||
epoch_length: number,
|
||||
// ideally I'd want to define those as `number` rather than `string`, but
|
||||
// rust-side they are defined as Uint128 and Decimal that don't have
|
||||
// native javascript representations and therefore are interpreted as strings after deserialization
|
||||
minimum_mixnode_bond: string,
|
||||
minimum_gateway_bond: string,
|
||||
mixnode_bond_reward_rate: string,
|
||||
gateway_bond_reward_rate: string,
|
||||
mixnode_delegation_reward_rate: string,
|
||||
gateway_delegation_reward_rate: string,
|
||||
mixnode_active_set_size: number,
|
||||
gateway_active_set_size: number,
|
||||
}
|
||||
export type ContractStateParams = {
|
||||
// ideally I'd want to define those as `number` rather than `string`, but
|
||||
// rust-side they are defined as Uint128 and that don't have
|
||||
// native javascript representations and therefore are interpreted as strings after deserialization
|
||||
minimum_mixnode_pledge: string;
|
||||
minimum_gateway_pledge: string;
|
||||
mixnode_rewarded_set_size: number;
|
||||
mixnode_active_set_size: number;
|
||||
};
|
||||
|
||||
export type RewardingIntervalResponse = {
|
||||
current_rewarding_interval_starting_block: number;
|
||||
current_rewarding_interval_nonce: number;
|
||||
rewarding_in_progress: boolean;
|
||||
};
|
||||
|
||||
export type LayerDistribution = {
|
||||
gateways: number;
|
||||
layer1: number;
|
||||
layer2: number;
|
||||
layer3: number;
|
||||
};
|
||||
|
||||
export type Delegation = {
|
||||
owner: string,
|
||||
amount: Coin,
|
||||
}
|
||||
owner: string;
|
||||
node_identity: string;
|
||||
amount: Coin;
|
||||
block_height: number;
|
||||
proxy?: string;
|
||||
};
|
||||
|
||||
export type PagedMixDelegationsResponse = {
|
||||
node_owner: string,
|
||||
delegations: Delegation[],
|
||||
start_next_after: string
|
||||
}
|
||||
delegations: Delegation[];
|
||||
start_next_after?: string;
|
||||
};
|
||||
|
||||
export type PagedGatewayDelegationsResponse = {
|
||||
node_owner: string,
|
||||
delegations: Delegation[],
|
||||
start_next_after: string
|
||||
}
|
||||
export type PagedDelegatorDelegationsResponse = {
|
||||
delegations: Delegation[];
|
||||
start_next_after?: string;
|
||||
};
|
||||
|
||||
export type PagedAllDelegationsResponse = {
|
||||
delegations: Delegation[];
|
||||
start_next_after?: [string, string];
|
||||
};
|
||||
|
||||
export type RewardingResult = {
|
||||
operator_reward: string;
|
||||
total_delegator_reward: string;
|
||||
};
|
||||
|
||||
export type NodeRewardParams = {
|
||||
period_reward_pool: string;
|
||||
k: string;
|
||||
reward_blockstamp: number;
|
||||
circulating_supply: string;
|
||||
uptime: string;
|
||||
sybil_resistance_percent: number;
|
||||
};
|
||||
|
||||
export type DelegatorRewardParams = {
|
||||
node_reward_params: NodeRewardParams;
|
||||
sigma: number;
|
||||
profit_margin: number;
|
||||
node_profit: number;
|
||||
};
|
||||
|
||||
export type PendingDelegatorRewarding = {
|
||||
running_results: RewardingResult;
|
||||
next_start: string;
|
||||
rewarding_params: DelegatorRewardParams;
|
||||
};
|
||||
|
||||
export type RewardingStatus = { Complete: RewardingResult } | { PendingNextDelegatorPage: PendingDelegatorRewarding };
|
||||
|
||||
export type MixnodeRewardingStatusResponse = {
|
||||
status?: RewardingStatus;
|
||||
};
|
||||
|
||||
export enum Layer {
|
||||
Gateway,
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Gateway,
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
}
|
||||
|
||||
export type MixNodeBond = { // TODO: change name to MixNodeBond
|
||||
owner: string,
|
||||
mix_node: MixNode, // TODO: camelCase this later once everything else works
|
||||
layer: Layer,
|
||||
bond_amount: Coin,
|
||||
total_delegation: Coin,
|
||||
}
|
||||
export type MixNodeBond = {
|
||||
owner: string;
|
||||
mix_node: MixNode;
|
||||
layer: Layer;
|
||||
bond_amount: Coin;
|
||||
total_delegation: Coin;
|
||||
};
|
||||
|
||||
export type MixNode = {
|
||||
host: string,
|
||||
mix_port: number,
|
||||
verloc_port: number,
|
||||
http_api_port: number,
|
||||
sphinx_key: string, // TODO: camelCase this later once everything else works
|
||||
identity_key: string,
|
||||
version: string,
|
||||
}
|
||||
host: string;
|
||||
mix_port: number;
|
||||
verloc_port: number;
|
||||
http_api_port: number;
|
||||
sphinx_key: string;
|
||||
identity_key: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
export type GatewayBond = {
|
||||
owner: string
|
||||
gateway: Gateway,
|
||||
owner: string;
|
||||
gateway: Gateway;
|
||||
|
||||
bond_amount: Coin,
|
||||
total_delegation: Coin,
|
||||
}
|
||||
bond_amount: Coin;
|
||||
total_delegation: Coin;
|
||||
};
|
||||
|
||||
export type Gateway = {
|
||||
host: string,
|
||||
mix_port: number,
|
||||
clients_port: number,
|
||||
location: string,
|
||||
sphinx_key: string,
|
||||
identity_key: string,
|
||||
version: string
|
||||
}
|
||||
host: string;
|
||||
mix_port: number;
|
||||
clients_port: number;
|
||||
location: string;
|
||||
sphinx_key: string;
|
||||
identity_key: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
export type SendRequest = {
|
||||
senderAddress: string,
|
||||
recipientAddress: string,
|
||||
transferAmount: readonly Coin[]
|
||||
}
|
||||
senderAddress: string;
|
||||
recipientAddress: string;
|
||||
transferAmount: readonly Coin[];
|
||||
};
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types
|
||||
import { Coin } from "@cosmjs/stargate";
|
||||
import { EncodeObject } from "@cosmjs/proto-signing";
|
||||
import { Coin } from '@cosmjs/stargate';
|
||||
import { EncodeObject } from '@cosmjs/proto-signing';
|
||||
|
||||
export function makeBankMsgSend(senderAddress: string, recipientAddress: string, transferAmount: readonly Coin[]): EncodeObject {
|
||||
return {
|
||||
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
|
||||
value: {
|
||||
fromAddress: senderAddress,
|
||||
toAddress: recipientAddress,
|
||||
amount: transferAmount,
|
||||
},
|
||||
};
|
||||
}
|
||||
export function makeBankMsgSend(
|
||||
senderAddress: string,
|
||||
recipientAddress: string,
|
||||
transferAmount: readonly Coin[],
|
||||
): EncodeObject {
|
||||
return {
|
||||
typeUrl: '/cosmos.bank.v1beta1.MsgSend',
|
||||
value: {
|
||||
fromAddress: senderAddress,
|
||||
toAddress: recipientAddress,
|
||||
amount: transferAmount,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import { GatewayBond, MixNodeBond } from './types';
|
||||
|
||||
export const VALIDATOR_API_VERSION = '/v1';
|
||||
export const VALIDATOR_API_GATEWAYS_PATH = `${VALIDATOR_API_VERSION}/gateways`;
|
||||
export const VALIDATOR_API_MIXNODES_PATH = `${VALIDATOR_API_VERSION}/mixnodes`;
|
||||
export const VALIDATOR_API_ACTIVE_MIXNODES_PATH = `${VALIDATOR_API_VERSION}/mixnodes/active`;
|
||||
export const VALIDATOR_API_REWARDED_MIXNODES_PATH = `${VALIDATOR_API_VERSION}/mixnodes/rewarded`;
|
||||
|
||||
export interface IValidatorApiQuery {
|
||||
getCachedMixnodes(): Promise<MixNodeBond[]>;
|
||||
|
||||
getCachedGateways(): Promise<GatewayBond[]>;
|
||||
|
||||
getActiveMixnodes(): Promise<MixNodeBond[]>;
|
||||
|
||||
getRewardedMixnodes(): Promise<MixNodeBond[]>;
|
||||
}
|
||||
|
||||
export default class ValidatorApiQuerier implements IValidatorApiQuery {
|
||||
validatorApiUrl: string;
|
||||
|
||||
constructor(validatorApiUrl: string) {
|
||||
this.validatorApiUrl = validatorApiUrl;
|
||||
}
|
||||
|
||||
async getCachedMixnodes(): Promise<MixNodeBond[]> {
|
||||
const url = new URL(this.validatorApiUrl);
|
||||
url.pathname += VALIDATOR_API_MIXNODES_PATH;
|
||||
|
||||
const response = await axios.get(url.toString());
|
||||
if (response.status === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error('None of the provided validator APIs seem to be alive');
|
||||
}
|
||||
|
||||
async getCachedGateways(): Promise<GatewayBond[]> {
|
||||
const url = new URL(this.validatorApiUrl);
|
||||
url.pathname += VALIDATOR_API_GATEWAYS_PATH;
|
||||
|
||||
const response = await axios.get(url.toString());
|
||||
if (response.status === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error('None of the provided validator APIs seem to be alive');
|
||||
}
|
||||
|
||||
async getActiveMixnodes(): Promise<MixNodeBond[]> {
|
||||
const url = new URL(this.validatorApiUrl);
|
||||
url.pathname += VALIDATOR_API_ACTIVE_MIXNODES_PATH;
|
||||
|
||||
const response = await axios.get(url.toString());
|
||||
if (response.status === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error('None of the provided validator APIs seem to be alive');
|
||||
}
|
||||
|
||||
async getRewardedMixnodes(): Promise<MixNodeBond[]> {
|
||||
const url = new URL(this.validatorApiUrl);
|
||||
url.pathname += VALIDATOR_API_REWARDED_MIXNODES_PATH;
|
||||
|
||||
const response = await axios.get(url.toString());
|
||||
if (response.status === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error('None of the provided validator APIs seem to be alive');
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
import { assert } from "chai";
|
||||
import INetClient from "../../src/net-client";
|
||||
import { Fixtures } from "../fixtures"
|
||||
import { Mock, Times } from "moq.ts";
|
||||
import GatewaysCache from "../../src/caches/gateways";
|
||||
|
||||
describe("Caching gateways: when the validator returns", () => {
|
||||
context("an empty list", () => {
|
||||
it("Should return an empty list", async () => {
|
||||
const perPage = 100;
|
||||
const contractAddress = "mockContractAddress";
|
||||
const emptyPromise = Promise.resolve(Fixtures.GatewaysResp.empty());
|
||||
const mockClient = new Mock<INetClient>().setup(netClient => netClient.getGateways(contractAddress, perPage, undefined)).returns(emptyPromise);
|
||||
const cache = new GatewaysCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshGateways(contractAddress);
|
||||
|
||||
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
assert.deepEqual([], cache.gateways);
|
||||
});
|
||||
})
|
||||
|
||||
context("a list of gatways that fits in a page", () => {
|
||||
it("Should return that one page list", async () => {
|
||||
const perPage = 2;
|
||||
const contractAddress = "mockContractAddress";
|
||||
const onePagePromise = Promise.resolve(Fixtures.GatewaysResp.onePage());
|
||||
const mockClient = new Mock<INetClient>().setup(netClient => netClient.getGateways(contractAddress, perPage, undefined)).returns(onePagePromise);
|
||||
const cache = new GatewaysCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshGateways(contractAddress);
|
||||
|
||||
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
assert.deepEqual(Fixtures.Gateways.list2(), cache.gateways);
|
||||
})
|
||||
})
|
||||
|
||||
context("a list of gateways that is longer than one page", () => {
|
||||
it("Should return the full list assembled from all pages", async () => {
|
||||
const perPage = 2; // we get back 2 per page
|
||||
const contractAddress = "mockContractAddress";
|
||||
const fullPageResult = Fixtures.GatewaysResp.page1of2();
|
||||
const halfPageResult = Fixtures.GatewaysResp.halfPage2of2();
|
||||
const mockClient = new Mock<INetClient>()
|
||||
mockClient.setup(instance => instance.getGateways(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult));
|
||||
mockClient.setup(instance => instance.getGateways(contractAddress, perPage, fullPageResult.start_next_after)).returns(Promise.resolve(halfPageResult));
|
||||
const cache = new GatewaysCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshGateways(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
|
||||
mockClient.verify(instance => instance.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
mockClient.verify(instance => instance.getGateways(contractAddress, perPage, fullPageResult.start_next_after), Times.Exactly(1));
|
||||
|
||||
assert.deepEqual(Fixtures.Gateways.list3(), cache.gateways); // there are a total of 3 nodes in the validator lists, we get them all back
|
||||
})
|
||||
})
|
||||
|
||||
context("a list of gateways that is two filled pages", () => {
|
||||
it("Should return the full list assembled from all pages", async () => {
|
||||
const perPage = 2; // we get back 2 per page
|
||||
const contractAddress = "mockContractAddress";
|
||||
const fullPageResult1 = Fixtures.GatewaysResp.page1of2();
|
||||
const fullPageResult2 = Fixtures.GatewaysResp.fullPage2of2();
|
||||
const mockClient = new Mock<INetClient>()
|
||||
mockClient.setup(netClient => netClient.getGateways(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult1));
|
||||
mockClient.setup(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after)).returns(Promise.resolve(fullPageResult2));
|
||||
|
||||
const cache = new GatewaysCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshGateways(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
|
||||
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(1));
|
||||
|
||||
assert.deepEqual(Fixtures.Gateways.list4(), cache.gateways); // there are a total of 3 nodes in the validator lists, we get them all back
|
||||
})
|
||||
})
|
||||
|
||||
context("refreshing the cache twice", () => {
|
||||
it("returns one full list assembled from all pages", async () => {
|
||||
const perPage = 2; // we get back 2 per page
|
||||
const contractAddress = "mockContractAddress";
|
||||
const fullPageResult1 = Fixtures.GatewaysResp.page1of2();
|
||||
const fullPageResult2 = Fixtures.GatewaysResp.fullPage2of2();
|
||||
const mockClient = new Mock<INetClient>()
|
||||
mockClient.setup(netClient => netClient.getGateways(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult1));
|
||||
mockClient.setup(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after)).returns(Promise.resolve(fullPageResult2));
|
||||
|
||||
const cache = new GatewaysCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshGateways(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
|
||||
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(1));
|
||||
|
||||
await cache.refreshGateways(contractAddress);
|
||||
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(2));
|
||||
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(2));
|
||||
|
||||
assert.deepEqual(Fixtures.Gateways.list4(), cache.gateways); // there are a total of 3 nodes in the validator lists, we get them all back
|
||||
})
|
||||
})
|
||||
});
|
||||
@@ -1,96 +0,0 @@
|
||||
import { assert } from "chai";
|
||||
import INetClient from "../../src/net-client";
|
||||
import { Fixtures } from "../fixtures"
|
||||
import { Mock, Times } from "moq.ts";
|
||||
import { MixnodesCache } from "../../src/caches/mixnodes"
|
||||
|
||||
describe("Caching mixnodes: when the validator returns", () => {
|
||||
context("an empty list", () => {
|
||||
it("Should return an empty list", async () => {
|
||||
const perPage = 100;
|
||||
const contractAddress = "mockContractAddress";
|
||||
const emptyPromise = Promise.resolve(Fixtures.MixNodesResp.empty());
|
||||
const mockClient = new Mock<INetClient>().setup(netClient => netClient.getMixNodes(contractAddress, perPage, undefined)).returns(emptyPromise);
|
||||
const cache = new MixnodesCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshMixNodes(contractAddress);
|
||||
|
||||
mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
assert.deepEqual([], cache.mixNodes);
|
||||
});
|
||||
})
|
||||
context("a list of nodes that fits in a page", () => {
|
||||
it("Should return that one page list", async () => {
|
||||
const perPage = 2;
|
||||
const contractAddress = "mockContractAddress";
|
||||
const onePagePromise = Promise.resolve(Fixtures.MixNodesResp.onePage());
|
||||
const mockClient = new Mock<INetClient>().setup(netClient => netClient.getMixNodes(contractAddress, perPage, undefined)).returns(onePagePromise);
|
||||
const cache = new MixnodesCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshMixNodes(contractAddress);
|
||||
|
||||
mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
assert.deepEqual(Fixtures.MixNodes.list2(), cache.mixNodes);
|
||||
})
|
||||
})
|
||||
|
||||
context("a list of nodes that is longer than one page", () => {
|
||||
it("Should return the full list assembled from all pages", async () => {
|
||||
const perPage = 2; // we get back 2 per page
|
||||
const contractAddress = "mockContractAddress";
|
||||
const fullPageResult = Fixtures.MixNodesResp.page1of2();
|
||||
const halfPageResult = Fixtures.MixNodesResp.halfPage2of2();
|
||||
const mockClient = new Mock<INetClient>()
|
||||
mockClient.setup(instance => instance.getMixNodes(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult));
|
||||
mockClient.setup(instance => instance.getMixNodes(contractAddress, perPage, fullPageResult.start_next_after)).returns(Promise.resolve(halfPageResult));
|
||||
const cache = new MixnodesCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshMixNodes(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
|
||||
mockClient.verify(instance => instance.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
mockClient.verify(instance => instance.getMixNodes(contractAddress, perPage, fullPageResult.start_next_after), Times.Exactly(1));
|
||||
|
||||
assert.deepEqual(Fixtures.MixNodes.list3(), cache.mixNodes); // there are a total of 3 nodes in the validator lists, we get them all back
|
||||
})
|
||||
})
|
||||
|
||||
context("a list of nodes that is two filled pages", () => {
|
||||
it("Should return the full list assembled from all pages", async () => {
|
||||
const perPage = 2; // we get back 2 per page
|
||||
const contractAddress = "mockContractAddress";
|
||||
const fullPageResult1 = Fixtures.MixNodesResp.page1of2();
|
||||
const fullPageResult2 = Fixtures.MixNodesResp.fullPage2of2();
|
||||
const mockClient = new Mock<INetClient>()
|
||||
mockClient.setup(netClient => netClient.getMixNodes(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult1));
|
||||
mockClient.setup(netClient => netClient.getMixNodes(contractAddress, perPage, fullPageResult1.start_next_after)).returns(Promise.resolve(fullPageResult2));
|
||||
|
||||
const cache = new MixnodesCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshMixNodes(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
|
||||
mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(1));
|
||||
|
||||
assert.deepEqual(Fixtures.MixNodes.list4(), cache.mixNodes); // there are a total of 3 nodes in the validator lists, we get them all back
|
||||
})
|
||||
})
|
||||
|
||||
context("refreshing the cache twice", () => {
|
||||
it("returns one full list assembled from all pages", async () => {
|
||||
const perPage = 2; // we get back 2 per page
|
||||
const contractAddress = "mockContractAddress";
|
||||
const fullPageResult1 = Fixtures.MixNodesResp.page1of2();
|
||||
const fullPageResult2 = Fixtures.MixNodesResp.fullPage2of2();
|
||||
const mockClient = new Mock<INetClient>()
|
||||
mockClient.setup(netClient => netClient.getMixNodes(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult1));
|
||||
mockClient.setup(netClient => netClient.getMixNodes(contractAddress, perPage, fullPageResult1.start_next_after)).returns(Promise.resolve(fullPageResult2));
|
||||
|
||||
const cache = new MixnodesCache(mockClient.object(), perPage);
|
||||
|
||||
await cache.refreshMixNodes(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
|
||||
await cache.refreshMixNodes(contractAddress);
|
||||
// mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
|
||||
// mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(1));
|
||||
|
||||
assert.deepEqual(Fixtures.MixNodes.list4(), cache.mixNodes); // there are a total of 3 nodes in the validator lists, we get them all back
|
||||
})
|
||||
})
|
||||
});
|
||||
@@ -1,156 +0,0 @@
|
||||
import { coins } from "@cosmjs/launchpad";
|
||||
import {PagedGatewayResponse, PagedMixnodeResponse} from "../src/net-client";
|
||||
import {GatewayBond, MixNodeBond} from "../src/types"
|
||||
|
||||
export namespace Fixtures {
|
||||
export class MixNodes {
|
||||
static single(): MixNodeBond {
|
||||
return {
|
||||
amount: coins(666, "unym"),
|
||||
owner: "bob",
|
||||
mix_node: {
|
||||
host: "1.1.1.1",
|
||||
layer: 1,
|
||||
location: "London, UK",
|
||||
sphinx_key: "foo",
|
||||
version: "0.10.0",
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static list1(): MixNodeBond[] {
|
||||
return [MixNodes.single()]
|
||||
}
|
||||
|
||||
static list2(): MixNodeBond[] {
|
||||
return [MixNodes.single(), MixNodes.single()]
|
||||
}
|
||||
|
||||
static list3(): MixNodeBond[] {
|
||||
return [MixNodes.single(), MixNodes.single(), MixNodes.single()]
|
||||
}
|
||||
|
||||
static list4(): MixNodeBond[] {
|
||||
return [MixNodes.single(), MixNodes.single(), MixNodes.single(), MixNodes.single()]
|
||||
}
|
||||
}
|
||||
|
||||
export class MixNodesResp {
|
||||
static empty(): PagedResponse {
|
||||
return {
|
||||
nodes: [],
|
||||
per_page: 2,
|
||||
start_next_after: null,
|
||||
}
|
||||
}
|
||||
|
||||
static onePage(): PagedResponse {
|
||||
return {
|
||||
nodes: MixNodes.list2(),
|
||||
per_page: 2,
|
||||
start_next_after: null
|
||||
}
|
||||
}
|
||||
|
||||
static page1of2(): PagedResponse {
|
||||
return {
|
||||
nodes: MixNodes.list2(),
|
||||
per_page: 2,
|
||||
start_next_after: "2"
|
||||
}
|
||||
}
|
||||
|
||||
static halfPage2of2(): PagedResponse {
|
||||
return {
|
||||
nodes: MixNodes.list1(),
|
||||
per_page: 2,
|
||||
start_next_after: null
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static fullPage2of2(): PagedResponse {
|
||||
return {
|
||||
nodes: MixNodes.list2(),
|
||||
per_page: 2,
|
||||
start_next_after: null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Gateways {
|
||||
static single(): GatewayBond {
|
||||
return {
|
||||
amount: coins(666, "unym"),
|
||||
owner: "bob",
|
||||
gateway: {
|
||||
mix_host: "1.1.1.1:1234",
|
||||
clients_host: "ws://1.1.1.1:1235",
|
||||
location: "London, UK",
|
||||
identity_key: "bar",
|
||||
sphinx_key: "foo",
|
||||
version: "0.10.0",
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static list1(): GatewayBond[] {
|
||||
return [Gateways.single()]
|
||||
}
|
||||
|
||||
static list2(): GatewayBond[] {
|
||||
return [Gateways.single(), Gateways.single()]
|
||||
}
|
||||
|
||||
static list3(): GatewayBond[] {
|
||||
return [Gateways.single(), Gateways.single(), Gateways.single()]
|
||||
}
|
||||
|
||||
static list4(): GatewayBond[] {
|
||||
return [Gateways.single(), Gateways.single(), Gateways.single(), Gateways.single()]
|
||||
}
|
||||
}
|
||||
|
||||
export class GatewaysResp {
|
||||
static empty(): PagedGatewayResponse {
|
||||
return {
|
||||
nodes: [],
|
||||
per_page: 2,
|
||||
start_next_after: "",
|
||||
}
|
||||
}
|
||||
|
||||
static onePage(): PagedGatewayResponse {
|
||||
return {
|
||||
nodes: Gateways.list2(),
|
||||
per_page: 2,
|
||||
start_next_after: "",
|
||||
}
|
||||
}
|
||||
|
||||
static page1of2(): PagedGatewayResponse {
|
||||
return {
|
||||
nodes: Gateways.list2(),
|
||||
per_page: 2,
|
||||
start_next_after: "2"
|
||||
}
|
||||
}
|
||||
|
||||
static halfPage2of2(): PagedGatewayResponse {
|
||||
return {
|
||||
nodes: Gateways.list1(),
|
||||
per_page: 2,
|
||||
start_next_after: "",
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static fullPage2of2(): PagedGatewayResponse {
|
||||
return {
|
||||
nodes: Gateways.list2(),
|
||||
per_page: 2,
|
||||
start_next_after: "",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2936
-2422
File diff suppressed because it is too large
Load Diff
@@ -234,11 +234,25 @@ impl<C> NymdClient<C> {
|
||||
.map(|block| block.block_id.hash)
|
||||
}
|
||||
|
||||
pub async fn get_balance(&self, address: &AccountId) -> Result<Option<CosmosCoin>, NymdError>
|
||||
pub async fn get_balance(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
denom: Denom,
|
||||
) -> Result<Option<CosmosCoin>, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
self.client.get_balance(address, self.denom()?).await
|
||||
self.client.get_balance(address, denom).await
|
||||
}
|
||||
|
||||
pub async fn get_mixnet_balance(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
) -> Result<Option<CosmosCoin>, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
self.get_balance(address, self.denom()?).await
|
||||
}
|
||||
|
||||
pub async fn get_contract_settings(&self) -> Result<ContractStateParams, NymdError>
|
||||
|
||||
@@ -207,9 +207,9 @@ mod tests {
|
||||
fn generating_account_addresses() {
|
||||
// test vectors produced from our js wallet
|
||||
let mnemonic_address = vec![
|
||||
("crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove", "punk1jw6mp7d5xqc7w6xm79lha27glmd0vdt32a3fj2"),
|
||||
("acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel", "punk1h5hgn94nsq4kh99rjj794hr5h5q6yfm22mcqqn"),
|
||||
("step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball", "punk17n9flp6jflljg6fp05dsy07wcprf2uuujse962")
|
||||
("crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove", "nymt1jw6mp7d5xqc7w6xm79lha27glmd0vdt339me94"),
|
||||
("acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel", "nymt1h5hgn94nsq4kh99rjj794hr5h5q6yfm23rjshv"),
|
||||
("step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball", "nymt17n9flp6jflljg6fp05dsy07wcprf2uuufgn4d4")
|
||||
];
|
||||
|
||||
for (mnemonic, address) in mnemonic_address.into_iter() {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
fn main() {
|
||||
match option_env!("NETWORK") {
|
||||
None | Some("milhon") => println!("cargo:rustc-cfg=network=\"milhon\"",),
|
||||
Some("sandbox") => println!("cargo:rustc-cfg=network=\"sandbox\"",),
|
||||
Some("qa") => println!("cargo:rustc-cfg=network=\"qa\""),
|
||||
_ => panic!("No such network"),
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,15 @@ use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
|
||||
pub mod eth_contract;
|
||||
#[cfg(network = "milhon")]
|
||||
pub mod milhon;
|
||||
#[cfg(network = "sandbox")]
|
||||
pub mod sandbox;
|
||||
|
||||
#[cfg(network = "milhon")]
|
||||
pub use milhon::*;
|
||||
#[cfg(network = "sandbox")]
|
||||
pub use sandbox::*;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ValidatorDetails {
|
||||
@@ -38,6 +47,7 @@ impl ValidatorDetails {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(network = "milhon")]
|
||||
pub fn default_validators() -> Vec<ValidatorDetails> {
|
||||
vec![
|
||||
ValidatorDetails::new(
|
||||
@@ -48,6 +58,14 @@ pub fn default_validators() -> Vec<ValidatorDetails> {
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(network = "sandbox")]
|
||||
pub fn default_validators() -> Vec<ValidatorDetails> {
|
||||
vec![ValidatorDetails::new(
|
||||
"https://sandbox-validator.nymtech.net",
|
||||
Some("https://sandbox-validator.nymtech.net/api"),
|
||||
)]
|
||||
}
|
||||
|
||||
pub fn default_nymd_endpoints() -> Vec<Url> {
|
||||
default_validators()
|
||||
.iter()
|
||||
@@ -62,10 +80,7 @@ pub fn default_api_endpoints() -> Vec<Url> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = "punk10pyejy66429refv3g35g2t7am0was7yalwrzen";
|
||||
pub const DEFAULT_VESTING_CONTRACT_ADDRESS: &str = "";
|
||||
pub const REWARDING_VALIDATOR_ADDRESS: &str = "punk1v9qauwdq5terag6uvfsdytcs2d0sdmfdy7hgk3";
|
||||
|
||||
// Ethereum constants used for token bridge
|
||||
/// How much bandwidth (in bytes) one token can buy
|
||||
const BYTES_PER_TOKEN: u64 = 1024 * 1024 * 1024;
|
||||
/// How many ERC20 tokens should be burned to buy bandwidth
|
||||
@@ -73,20 +88,10 @@ pub const TOKENS_TO_BURN: u64 = 10;
|
||||
/// Default bandwidth (in bytes) that we try to buy
|
||||
pub const BANDWIDTH_VALUE: u64 = TOKENS_TO_BURN * BYTES_PER_TOKEN;
|
||||
|
||||
// Ethereum constants used for token bridge
|
||||
pub const ETH_CONTRACT_ADDRESS: [u8; 20] =
|
||||
hex_literal::hex!("9fEE3e28c17dbB87310A51F13C4fbf4331A6f102");
|
||||
pub const ETH_MIN_BLOCK_DEPTH: usize = 7;
|
||||
pub const COSMOS_CONTRACT_ADDRESS: &str = "punk1jld76tqw4wnpfenmay2xkv86nr3j0w426eka82";
|
||||
// Name of the event triggered by the eth contract. If the event name is changed,
|
||||
// this would also need to be changed; It is currently tested against the json abi
|
||||
pub const ETH_EVENT_NAME: &str = "Burned";
|
||||
pub const ETH_BURN_FUNCTION_NAME: &str = "burnTokenForAccessCode";
|
||||
|
||||
/// Defaults Cosmos Hub/ATOM path
|
||||
pub const COSMOS_DERIVATION_PATH: &str = "m/44'/118'/0'/0/0";
|
||||
pub const BECH32_PREFIX: &str = "punk";
|
||||
pub const DENOM: &str = "upunk";
|
||||
// as set by validators in their configs
|
||||
// (note that the 'amount' postfix is relevant here as the full gas price also includes denom)
|
||||
pub const GAS_PRICE_AMOUNT: f64 = 0.025;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub const BECH32_PREFIX: &str = "punk";
|
||||
pub const DENOM: &str = "upunk";
|
||||
|
||||
pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = "punk10pyejy66429refv3g35g2t7am0was7yalwrzen";
|
||||
pub const DEFAULT_VESTING_CONTRACT_ADDRESS: &str = "";
|
||||
pub const COSMOS_CONTRACT_ADDRESS: &str = "punk1jld76tqw4wnpfenmay2xkv86nr3j0w426eka82";
|
||||
pub const REWARDING_VALIDATOR_ADDRESS: &str = "punk1v9qauwdq5terag6uvfsdytcs2d0sdmfdy7hgk3";
|
||||
pub const ETH_CONTRACT_ADDRESS: [u8; 20] =
|
||||
hex_literal::hex!("9fEE3e28c17dbB87310A51F13C4fbf4331A6f102");
|
||||
|
||||
// Name of the event triggered by the eth contract. If the event name is changed,
|
||||
// this would also need to be changed; It is currently tested against the json abi
|
||||
pub const ETH_EVENT_NAME: &str = "Burned";
|
||||
pub const ETH_BURN_FUNCTION_NAME: &str = "burnTokenForAccessCode";
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub const BECH32_PREFIX: &str = "nymt";
|
||||
pub const DENOM: &str = "unymt";
|
||||
|
||||
pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = "nymt14hj2tavq8fpesdwxxcu44rty3hh90vhuysqrsr";
|
||||
pub const DEFAULT_VESTING_CONTRACT_ADDRESS: &str = "nymt1nc5tatafv6eyq7llkr2gv50ff9e22mnfp9pc5s";
|
||||
pub const COSMOS_CONTRACT_ADDRESS: &str = "nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv";
|
||||
pub const REWARDING_VALIDATOR_ADDRESS: &str = "nymt17zujduc46wvkwvp6f062mm5xhr7jc3fewvqu9e";
|
||||
pub const ETH_CONTRACT_ADDRESS: [u8; 20] =
|
||||
hex_literal::hex!("9fEE3e28c17dbB87310A51F13C4fbf4331A6f102");
|
||||
|
||||
// Name of the event triggered by the eth contract. If the event name is changed,
|
||||
// this would also need to be changed; It is currently tested against the json abi
|
||||
pub const ETH_EVENT_NAME: &str = "Burned";
|
||||
pub const ETH_BURN_FUNCTION_NAME: &str = "burnTokenForAccessCode";
|
||||
@@ -56,14 +56,7 @@ pub(crate) fn try_delegate_to_mixnode_on_behalf(
|
||||
// check if the delegation contains any funds of the appropriate denomination
|
||||
let amount = validate_delegation_stake(info.funds)?;
|
||||
|
||||
_try_delegate_to_mixnode(
|
||||
deps,
|
||||
env,
|
||||
mix_identity,
|
||||
&delegate,
|
||||
amount,
|
||||
Some(info.sender),
|
||||
)
|
||||
_try_delegate_to_mixnode(deps, env, mix_identity, &delegate, amount, None)
|
||||
}
|
||||
|
||||
pub(crate) fn _try_delegate_to_mixnode(
|
||||
|
||||
@@ -52,16 +52,8 @@ pub fn try_add_gateway_on_behalf(
|
||||
.minimum_mixnode_pledge;
|
||||
let pledge = validate_gateway_pledge(info.funds, minimum_pledge)?;
|
||||
|
||||
let proxy = info.sender;
|
||||
_try_add_gateway(
|
||||
deps,
|
||||
env,
|
||||
gateway,
|
||||
pledge,
|
||||
&owner,
|
||||
owner_signature,
|
||||
Some(proxy),
|
||||
)
|
||||
let _proxy = info.sender;
|
||||
_try_add_gateway(deps, env, gateway, pledge, &owner, owner_signature, None)
|
||||
}
|
||||
|
||||
pub(crate) fn _try_add_gateway(
|
||||
|
||||
@@ -54,16 +54,8 @@ pub fn try_add_mixnode_on_behalf(
|
||||
.minimum_mixnode_pledge;
|
||||
let pledge = validate_mixnode_pledge(info.funds, minimum_pledge)?;
|
||||
|
||||
let proxy = info.sender;
|
||||
_try_add_mixnode(
|
||||
deps,
|
||||
env,
|
||||
mix_node,
|
||||
pledge,
|
||||
&owner,
|
||||
owner_signature,
|
||||
Some(proxy),
|
||||
)
|
||||
let _proxy = info.sender;
|
||||
_try_add_mixnode(deps, env, mix_node, pledge, &owner, owner_signature, None)
|
||||
}
|
||||
|
||||
fn _try_add_mixnode(
|
||||
|
||||
@@ -47,12 +47,15 @@ pub(crate) fn ensure_no_existing_bond(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
#[allow(unused_variables)]
|
||||
pub(crate) fn validate_node_identity_signature(
|
||||
deps: Deps,
|
||||
owner: &Addr,
|
||||
signature: String,
|
||||
identity: IdentityKeyRef,
|
||||
) -> Result<(), ContractError> {
|
||||
return Ok(());
|
||||
let owner_bytes = owner.as_bytes();
|
||||
|
||||
let mut identity_bytes = [0u8; 32];
|
||||
@@ -96,6 +99,7 @@ mod tests {
|
||||
use rand_chacha::rand_core::SeedableRng;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn validating_node_signature() {
|
||||
let deps = mock_dependencies();
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
FROM rust:1.56.1 as builder
|
||||
|
||||
ARG NYM_VERSION="develop"
|
||||
RUN git clone https://github.com/nymtech/nym.git && cd nym && git checkout $NYM_VERSION
|
||||
RUN cargo install --path /nym/gateway
|
||||
|
||||
FROM debian:buster-slim
|
||||
RUN apt-get update && apt-get install -y openssl ca-certificates && rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=builder /usr/local/cargo/bin/nym-gateway /usr/local/bin/nym-gateway
|
||||
ENTRYPOINT ["nym-gateway"]
|
||||
@@ -1,10 +0,0 @@
|
||||
FROM rust:1.56.1 as builder
|
||||
|
||||
ARG NYM_VERSION="develop"
|
||||
RUN git clone https://github.com/nymtech/nym.git && cd nym && git checkout $NYM_VERSION
|
||||
RUN cargo install --path /nym/mixnode
|
||||
|
||||
FROM debian:buster-slim
|
||||
RUN apt-get update && apt-get install -y openssl ca-certificates && rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=builder /usr/local/cargo/bin/nym-mixnode /usr/local/bin/nym-mixnode
|
||||
ENTRYPOINT ["nym-mixnode"]
|
||||
@@ -16,8 +16,8 @@ serde_json = "1.0.66"
|
||||
tokio = {version = "1.9.0", features = ["full"] }
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
schemars = { version = "0.8", features = ["preserve_order"] }
|
||||
okapi = { version = "0.6.0-alpha-1", features = ["derive_json_schema"] }
|
||||
rocket_okapi = "0.7.0-alpha-1"
|
||||
okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] }
|
||||
rocket_okapi = { version = "0.8.0-rc.1", features = ["swagger"] }
|
||||
log = "0.4.0"
|
||||
pretty_env_logger = "0.4.0"
|
||||
thiserror = "1.0.29"
|
||||
|
||||
@@ -15,6 +15,13 @@ impl GeoLocateTask {
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self) {
|
||||
if ::std::env::var("GEO_IP_SERVICE_API_KEY").is_err() {
|
||||
error!(
|
||||
"Env var GEO_IP_SERVICE_API_KEY is not set. Geolocation tasks will not be started."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
info!("Spawning mix node locator task runner...");
|
||||
tokio::spawn(async move {
|
||||
let mut interval_timer = tokio::time::interval(std::time::Duration::from_millis(50));
|
||||
|
||||
@@ -2,9 +2,12 @@ use crate::country_statistics::country_nodes_distribution::CountryNodesDistribut
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{Route, State};
|
||||
use rocket_okapi::okapi::openapi3::OpenApi;
|
||||
use rocket_okapi::openapi_get_routes_spec;
|
||||
use rocket_okapi::settings::OpenApiSettings;
|
||||
|
||||
pub fn country_statistics_make_default_routes() -> Vec<Route> {
|
||||
routes_with_openapi![index]
|
||||
pub fn country_statistics_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![settings: index]
|
||||
}
|
||||
|
||||
// We could either separate stuff by structure (like this, http is separate), or we could just
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use log::info;
|
||||
use rocket::http::Method;
|
||||
use rocket::Request;
|
||||
use rocket::{Build, Request, Rocket};
|
||||
use rocket_cors::{AllowedHeaders, AllowedOrigins};
|
||||
use rocket_okapi::swagger_ui::make_swagger_ui;
|
||||
|
||||
use crate::country_statistics::http::country_statistics_make_default_routes;
|
||||
use crate::http::swagger::get_docs;
|
||||
use crate::mix_node::http::mix_node_make_default_routes;
|
||||
use crate::mix_nodes::http::mix_nodes_make_default_routes;
|
||||
use crate::ping::http::ping_make_default_routes;
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
|
||||
@@ -15,35 +16,45 @@ mod swagger;
|
||||
pub(crate) fn start(state: ExplorerApiStateContext) {
|
||||
tokio::spawn(async move {
|
||||
info!("Starting up...");
|
||||
|
||||
let allowed_origins = AllowedOrigins::all();
|
||||
|
||||
// You can also deserialize this
|
||||
let cors = rocket_cors::CorsOptions {
|
||||
allowed_origins,
|
||||
allowed_methods: vec![Method::Get].into_iter().map(From::from).collect(),
|
||||
allowed_headers: AllowedHeaders::some(&["*"]),
|
||||
allow_credentials: true,
|
||||
..Default::default()
|
||||
}
|
||||
.to_cors()
|
||||
.unwrap();
|
||||
|
||||
let config = rocket::config::Config::release_default();
|
||||
rocket::build()
|
||||
.configure(config)
|
||||
.mount("/countries", country_statistics_make_default_routes())
|
||||
.mount("/ping", ping_make_default_routes())
|
||||
.mount("/mix-node", mix_node_make_default_routes())
|
||||
.mount("/swagger", make_swagger_ui(&get_docs()))
|
||||
.register("/", catchers![not_found])
|
||||
.manage(state)
|
||||
.attach(cors)
|
||||
.launch()
|
||||
.await
|
||||
configure_rocket(state).launch().await
|
||||
});
|
||||
}
|
||||
|
||||
fn configure_rocket(state: ExplorerApiStateContext) -> Rocket<Build> {
|
||||
let allowed_origins = AllowedOrigins::all();
|
||||
|
||||
// You can also deserialize this
|
||||
let cors = rocket_cors::CorsOptions {
|
||||
allowed_origins,
|
||||
allowed_methods: vec![Method::Get].into_iter().map(From::from).collect(),
|
||||
allowed_headers: AllowedHeaders::some(&["*"]),
|
||||
allow_credentials: true,
|
||||
..Default::default()
|
||||
}
|
||||
.to_cors()
|
||||
.unwrap();
|
||||
|
||||
let openapi_settings = rocket_okapi::settings::OpenApiSettings::default();
|
||||
let config = rocket::config::Config::release_default();
|
||||
let mut building_rocket = rocket::build().configure(config);
|
||||
|
||||
mount_endpoints_and_merged_docs! {
|
||||
building_rocket,
|
||||
"/v1".to_owned(),
|
||||
openapi_settings,
|
||||
"/ping" => ping_make_default_routes(&openapi_settings),
|
||||
"/countries" => country_statistics_make_default_routes(&openapi_settings),
|
||||
"/mix-node" => mix_node_make_default_routes(&openapi_settings),
|
||||
"/mix-nodes" => mix_nodes_make_default_routes(&openapi_settings),
|
||||
};
|
||||
|
||||
building_rocket
|
||||
.mount("/swagger", make_swagger_ui(&get_docs()))
|
||||
.register("/", catchers![not_found])
|
||||
.manage(state)
|
||||
.attach(cors)
|
||||
}
|
||||
|
||||
#[catch(404)]
|
||||
pub(crate) fn not_found(req: &Request) -> String {
|
||||
format!("I couldn't find '{}'. Try something else?", req.uri())
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
use rocket_okapi::swagger_ui::{SwaggerUIConfig, UrlObject};
|
||||
use rocket_okapi::swagger_ui::SwaggerUIConfig;
|
||||
|
||||
pub(crate) fn get_docs() -> SwaggerUIConfig {
|
||||
SwaggerUIConfig {
|
||||
urls: vec![
|
||||
UrlObject::new("Country statistics", "/countries/openapi.json"),
|
||||
UrlObject::new("Node ping", "/ping/openapi.json"),
|
||||
UrlObject::new("Mix node", "/mix-node/openapi.json"),
|
||||
],
|
||||
url: "../v1/openapi.json".to_owned(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,11 @@ impl ExplorerApi {
|
||||
async fn run(&mut self) {
|
||||
info!("Explorer API starting up...");
|
||||
|
||||
info!(
|
||||
"Using validator API - {}",
|
||||
network_defaults::default_api_endpoints()[0].clone()
|
||||
);
|
||||
|
||||
// spawn concurrent tasks
|
||||
mix_nodes::tasks::MixNodesTasks::new(self.state.clone()).start();
|
||||
country_statistics::distribution::CountryStatisticsDistributionTask::new(
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use reqwest::Error as ReqwestError;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{Route, State};
|
||||
use rocket_okapi::okapi::openapi3::OpenApi;
|
||||
use rocket_okapi::openapi_get_routes_spec;
|
||||
use rocket_okapi::settings::OpenApiSettings;
|
||||
use serde::Serialize;
|
||||
|
||||
use mixnet_contract::{Addr, Coin, Delegation, Layer, MixNode};
|
||||
@@ -9,13 +12,12 @@ use crate::mix_node::models::{NodeDescription, NodeStats};
|
||||
use crate::mix_nodes::{get_mixnode_delegations, get_single_mixnode_delegations, Location};
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
|
||||
pub fn mix_node_make_default_routes() -> Vec<Route> {
|
||||
routes_with_openapi![
|
||||
get_delegations,
|
||||
pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![
|
||||
settings: get_delegations,
|
||||
get_all_delegations,
|
||||
get_description,
|
||||
get_stats,
|
||||
list
|
||||
]
|
||||
}
|
||||
|
||||
@@ -29,14 +31,6 @@ pub(crate) struct PrettyMixNodeBondWithLocation {
|
||||
pub mix_node: MixNode,
|
||||
}
|
||||
|
||||
#[openapi(tag = "mix_node")]
|
||||
#[get("/")]
|
||||
pub(crate) async fn list(
|
||||
state: &State<ExplorerApiStateContext>,
|
||||
) -> Json<Vec<PrettyMixNodeBondWithLocation>> {
|
||||
Json(state.inner.mix_nodes.get_mixnodes_with_location().await)
|
||||
}
|
||||
|
||||
#[openapi(tag = "mix_node")]
|
||||
#[get("/<pubkey>/delegations")]
|
||||
pub(crate) async fn get_delegations(pubkey: &str) -> Json<Vec<Delegation>> {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{Route, State};
|
||||
use rocket_okapi::okapi::openapi3::OpenApi;
|
||||
use rocket_okapi::openapi_get_routes_spec;
|
||||
use rocket_okapi::settings::OpenApiSettings;
|
||||
|
||||
use crate::mix_node::http::PrettyMixNodeBondWithLocation;
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
|
||||
pub fn mix_nodes_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![settings: list]
|
||||
}
|
||||
|
||||
#[openapi(tag = "mix_nodes")]
|
||||
#[get("/")]
|
||||
pub(crate) async fn list(
|
||||
state: &State<ExplorerApiStateContext>,
|
||||
) -> Json<Vec<PrettyMixNodeBondWithLocation>> {
|
||||
Json(state.inner.mix_nodes.get_mixnodes_with_location().await)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub(crate) mod http;
|
||||
pub(crate) mod tasks;
|
||||
mod utils;
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ use std::time::Duration;
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{Route, State};
|
||||
use rocket_okapi::okapi::openapi3::OpenApi;
|
||||
use rocket_okapi::openapi_get_routes_spec;
|
||||
use rocket_okapi::settings::OpenApiSettings;
|
||||
|
||||
use mixnet_contract::MixNodeBond;
|
||||
|
||||
@@ -12,8 +15,8 @@ use crate::state::ExplorerApiStateContext;
|
||||
|
||||
const CONNECTION_TIMEOUT_SECONDS: Duration = Duration::from_secs(10);
|
||||
|
||||
pub fn ping_make_default_routes() -> Vec<Route> {
|
||||
routes_with_openapi![index]
|
||||
pub fn ping_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![settings: index]
|
||||
}
|
||||
|
||||
#[openapi(tag = "ping")]
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
EXPLORER_API_URL=https://testnet-milhon-explorer.nymtech.net
|
||||
VALIDATOR_API_URL=https://testnet-milhon-validator1.nymtech.net
|
||||
BIG_DIPPER_URL=https://testnet-milhon-blocks.nymtech.net
|
||||
+5
-3
@@ -1,3 +1,5 @@
|
||||
EXPLORER_API_URL=https://testnet-milhon-explorer.nymtech.net
|
||||
VALIDATOR_API_URL=https://testnet-milhon-validator1.nymtech.net
|
||||
BIG_DIPPER_URL=https://testnet-milhon-blocks.nymtech.net
|
||||
EXPLORER_API_URL=https://sandbox-explorer.nymtech.net/api/v1
|
||||
VALIDATOR_API_URL=https://sandbox-validator.nymtech.net
|
||||
BIG_DIPPER_URL=https://sandbox-blocks.nymtech.net
|
||||
CURRENCY_DENOM=unymt
|
||||
CURRENCY_STAKING_DENOM=unyxt
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
// master APIs
|
||||
export const MASTER_URL = process.env.EXPLORER_API_URL;
|
||||
export const MASTER_VALIDATOR_URL = process.env.VALIDATOR_API_URL;
|
||||
export const API_BASE_URL = process.env.EXPLORER_API_URL;
|
||||
export const VALIDATOR_API_BASE_URL = process.env.VALIDATOR_API_URL;
|
||||
export const BIG_DIPPER = process.env.BIG_DIPPER_URL;
|
||||
|
||||
// specific API routes
|
||||
export const MIXNODE_PING = `${MASTER_URL}/api/ping`;
|
||||
export const MIXNODES_API = `${MASTER_URL}/api/mix-node`;
|
||||
export const GATEWAYS_API = `${MASTER_VALIDATOR_URL}/api/v1/gateways`;
|
||||
export const VALIDATORS_API = `${MASTER_VALIDATOR_URL}/validators`;
|
||||
export const BLOCK_API = `${MASTER_VALIDATOR_URL}/block`;
|
||||
export const COUNTRY_DATA_API = `${MASTER_URL}/api/countries`;
|
||||
export const UPTIME_STORY_API = `${MASTER_VALIDATOR_URL}/api/v1/status/mixnode`; // add ID then '/history' to this.
|
||||
export const MIXNODE_PING = `${API_BASE_URL}/ping`;
|
||||
export const MIXNODES_API = `${API_BASE_URL}/mix-nodes`;
|
||||
export const MIXNODE_API = `${API_BASE_URL}/mix-node`;
|
||||
export const GATEWAYS_API = `${VALIDATOR_API_BASE_URL}/api/v1/gateways`;
|
||||
export const VALIDATORS_API = `${VALIDATOR_API_BASE_URL}/validators`;
|
||||
export const BLOCK_API = `${VALIDATOR_API_BASE_URL}/block`;
|
||||
export const COUNTRY_DATA_API = `${API_BASE_URL}/countries`;
|
||||
export const UPTIME_STORY_API = `${VALIDATOR_API_BASE_URL}/api/v1/status/mixnode`; // add ID then '/history' to this.
|
||||
|
||||
// errors
|
||||
export const MIXNODE_API_ERROR =
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
COUNTRY_DATA_API,
|
||||
MIXNODE_PING,
|
||||
UPTIME_STORY_API,
|
||||
MIXNODE_API,
|
||||
} from './constants';
|
||||
|
||||
import {
|
||||
@@ -87,10 +88,10 @@ export class Api {
|
||||
static fetchDelegationsById = async (
|
||||
id: string,
|
||||
): Promise<DelegationsResponse> =>
|
||||
(await fetch(`${MIXNODES_API}/${id}/delegations`)).json();
|
||||
(await fetch(`${MIXNODE_API}/${id}/delegations`)).json();
|
||||
|
||||
static fetchStatsById = async (id: string): Promise<StatsResponse> =>
|
||||
(await fetch(`${MIXNODES_API}/${id}/stats`)).json();
|
||||
(await fetch(`${MIXNODE_API}/${id}/stats`)).json();
|
||||
|
||||
static fetchStatusById = async (id: string): Promise<StatusResponse> =>
|
||||
(await fetch(`${MIXNODE_PING}/${id}`)).json();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { printableCoin } from '@nymproject/nym-validator-client';
|
||||
import { Alert, CircularProgress, useMediaQuery, Box } from '@mui/material';
|
||||
import { Alert, Box, CircularProgress, useMediaQuery } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
@@ -11,6 +10,7 @@ import TableRow from '@mui/material/TableRow';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import { useMainContext } from 'src/context/main';
|
||||
import { ExpandMore } from '@mui/icons-material';
|
||||
import { currencyToString } from '../utils/currency';
|
||||
|
||||
export const BondBreakdownTable: React.FC = () => {
|
||||
const { mixnodeDetailInfo, delegations } = useMainContext();
|
||||
@@ -34,24 +34,23 @@ export const BondBreakdownTable: React.FC = () => {
|
||||
const thisMixnode = mixnodeDetailInfo?.data[0];
|
||||
|
||||
// delegations
|
||||
const decimalisedDelegations = printableCoin({
|
||||
amount: thisMixnode.total_delegation.amount.toString(),
|
||||
denom: thisMixnode.total_delegation.denom,
|
||||
});
|
||||
const decimalisedDelegations = currencyToString(
|
||||
thisMixnode.total_delegation.amount.toString(),
|
||||
thisMixnode.total_delegation.denom,
|
||||
);
|
||||
|
||||
// pledges
|
||||
const decimalisedPledges = printableCoin({
|
||||
amount: thisMixnode.bond_amount.amount.toString(),
|
||||
denom: thisMixnode.bond_amount.denom,
|
||||
});
|
||||
const decimalisedPledges = currencyToString(
|
||||
thisMixnode.pledge_amount.amount.toString(),
|
||||
thisMixnode.pledge_amount.denom,
|
||||
);
|
||||
|
||||
// bonds total (del + pledges)
|
||||
const pledgesSum = Number(thisMixnode.bond_amount.amount);
|
||||
const pledgesSum = Number(thisMixnode.pledge_amount.amount);
|
||||
const delegationsSum = Number(thisMixnode.total_delegation.amount);
|
||||
const bondsTotal = printableCoin({
|
||||
amount: (delegationsSum + pledgesSum).toString(),
|
||||
denom: 'upunk',
|
||||
});
|
||||
const bondsTotal = currencyToString(
|
||||
(delegationsSum + pledgesSum).toString(),
|
||||
);
|
||||
|
||||
setBonds({
|
||||
delegations: decimalisedDelegations,
|
||||
@@ -89,7 +88,7 @@ export const BondBreakdownTable: React.FC = () => {
|
||||
mixnodeDetailInfo.data[0].total_delegation.amount,
|
||||
);
|
||||
const rawPledgeAmount = Number(
|
||||
mixnodeDetailInfo.data[0].bond_amount.amount,
|
||||
mixnodeDetailInfo.data[0].pledge_amount.amount,
|
||||
);
|
||||
const rawTotalBondsAmount = rawDelegationAmount + rawPledgeAmount;
|
||||
return ((num * 100) / rawTotalBondsAmount).toFixed(1);
|
||||
@@ -203,7 +202,7 @@ export const BondBreakdownTable: React.FC = () => {
|
||||
{owner}
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
{printableCoin({ amount: amount.toString(), denom })}
|
||||
{currencyToString(amount.toString(), denom)}
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
{calcBondPercentage(amount)}%
|
||||
|
||||
@@ -8,9 +8,9 @@ import {
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from '@mui/material';
|
||||
import { printableCoin } from '@nymproject/nym-validator-client';
|
||||
import { cellStyles } from './Universal-DataGrid';
|
||||
import { MixnodeRowType } from '../utils/index';
|
||||
import { currencyToString } from '../utils/currency';
|
||||
|
||||
export type ColumnsType = {
|
||||
field: string;
|
||||
@@ -28,7 +28,7 @@ export interface UniversalTableProps {
|
||||
|
||||
function formatCellValues(val: string | number, field: string) {
|
||||
if (field === 'bond') {
|
||||
return printableCoin({ amount: val.toString(), denom: 'upunk' });
|
||||
return currencyToString(val.toString());
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { Button, Card, Grid, Typography } from '@mui/material';
|
||||
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid';
|
||||
import { printableCoin } from '@nymproject/nym-validator-client';
|
||||
import { SelectChangeEvent } from '@mui/material/Select';
|
||||
import { useMainContext } from 'src/context/main';
|
||||
import { gatewayToGridRow } from 'src/utils';
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
cellStyles,
|
||||
UniversalDataGrid,
|
||||
} from 'src/components/Universal-DataGrid';
|
||||
import { currencyToString } from '../../utils/currency';
|
||||
|
||||
export const PageGateways: React.FC = () => {
|
||||
const { gateways } = useMainContext();
|
||||
@@ -79,17 +79,11 @@ export const PageGateways: React.FC = () => {
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Pledge" />,
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
headerAlign: 'left',
|
||||
renderCell: (params: GridRenderCellParams) => {
|
||||
const bondAsPunk = printableCoin({
|
||||
amount: params.value as string,
|
||||
denom: 'upunk',
|
||||
});
|
||||
return (
|
||||
<Typography sx={cellStyles} data-testid="pledge-amount">
|
||||
{bondAsPunk}
|
||||
</Typography>
|
||||
);
|
||||
},
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<Typography sx={cellStyles} data-testid="pledge-amount">
|
||||
{currencyToString(params.value)}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'host',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid';
|
||||
import { printableCoin } from '@nymproject/nym-validator-client';
|
||||
import { Button, Grid, Link as MuiLink, Card } from '@mui/material';
|
||||
import { Link as RRDLink } from 'react-router-dom';
|
||||
import { SelectChangeEvent } from '@mui/material/Select';
|
||||
@@ -15,6 +14,7 @@ import {
|
||||
UniversalDataGrid,
|
||||
cellStyles,
|
||||
} from 'src/components/Universal-DataGrid';
|
||||
import { currencyToString } from '../../utils/currency';
|
||||
|
||||
export const PageMixnodes: React.FC = () => {
|
||||
const { mixnodes } = useMainContext();
|
||||
@@ -92,21 +92,15 @@ export const PageMixnodes: React.FC = () => {
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
width: 150,
|
||||
headerAlign: 'left',
|
||||
renderCell: (params: GridRenderCellParams) => {
|
||||
const bondAsPunk = printableCoin({
|
||||
amount: params.value as string,
|
||||
denom: 'upunk',
|
||||
});
|
||||
return (
|
||||
<MuiLink
|
||||
sx={cellStyles}
|
||||
component={RRDLink}
|
||||
to={`/network-components/mixnodes/${params.row.identity_key}`}
|
||||
>
|
||||
{bondAsPunk}
|
||||
</MuiLink>
|
||||
);
|
||||
},
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<MuiLink
|
||||
sx={cellStyles}
|
||||
component={RRDLink}
|
||||
to={`/network-components/mixnodes/${params.row.identity_key}`}
|
||||
>
|
||||
{currencyToString(params.value)}
|
||||
</MuiLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'location',
|
||||
|
||||
@@ -7,13 +7,14 @@ export interface ClientConfig {
|
||||
|
||||
export interface MixNode {
|
||||
host: string;
|
||||
location: string;
|
||||
mix_port: number;
|
||||
http_api_port: number;
|
||||
verloc_port: number;
|
||||
sphinx_key: string;
|
||||
identity_key: string;
|
||||
version: string;
|
||||
mix_port: number;
|
||||
verloc_port: number;
|
||||
http_api_port: number;
|
||||
profit_margin_percent: number;
|
||||
location: string;
|
||||
}
|
||||
|
||||
export interface Gateway {
|
||||
@@ -32,7 +33,7 @@ export interface Amount {
|
||||
}
|
||||
|
||||
export interface MixNodeResponseItem {
|
||||
bond_amount: Amount;
|
||||
pledge_amount: Amount;
|
||||
total_delegation: Amount;
|
||||
owner: string;
|
||||
layer: string;
|
||||
@@ -74,7 +75,7 @@ export type MixNodeHistoryResponse = StatsResponse;
|
||||
|
||||
export interface GatewayResponseItem {
|
||||
block_height: number;
|
||||
bond_amount: Amount;
|
||||
pledge_amount: Amount;
|
||||
total_delegation: Amount;
|
||||
owner: string;
|
||||
gateway: Gateway;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { printableCoin } from '@nymproject/nym-validator-client';
|
||||
|
||||
const DENOM = process.env.CURRENCY_DENOM || 'unym';
|
||||
const DENOM_STAKING = process.env.CURRENCY_STAKING_DENOM || 'unyx';
|
||||
|
||||
export const currencyToString = (amount: string, denom: string = DENOM) =>
|
||||
printableCoin({
|
||||
amount,
|
||||
denom,
|
||||
});
|
||||
|
||||
export const stakingCurrencyToString = (
|
||||
amount: string,
|
||||
denom: string = DENOM_STAKING,
|
||||
) =>
|
||||
printableCoin({
|
||||
amount,
|
||||
denom,
|
||||
});
|
||||
@@ -69,7 +69,7 @@ export function mixnodeToGridRow(arrayOfMixnodes: MixNodeResponse): any {
|
||||
return !arrayOfMixnodes
|
||||
? []
|
||||
: arrayOfMixnodes.map((mn) => {
|
||||
const pledge = Number(mn.bond_amount.amount) || 0;
|
||||
const pledge = Number(mn.pledge_amount.amount) || 0;
|
||||
const delegations = Number(mn.total_delegation.amount) || 0;
|
||||
const totalBond = pledge + delegations;
|
||||
const selfPercentage = ((pledge * 100) / totalBond).toFixed(2);
|
||||
@@ -96,7 +96,7 @@ export function gatewayToGridRow(
|
||||
owner: gw.owner,
|
||||
identity_key: gw.gateway.identity_key || '',
|
||||
location: gw?.gateway?.location || '',
|
||||
bond: gw.bond_amount.amount || 0,
|
||||
bond: gw.pledge_amount.amount || 0,
|
||||
host: gw.gateway.host || '',
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -56,3 +56,4 @@ coconut = ["coconut-interface", "gateway-requests/coconut", "gateway-client/coco
|
||||
[build-dependencies]
|
||||
tokio = { version = "1.4", features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
|
||||
@@ -1,5 +1,6 @@
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
use std::env;
|
||||
use vergen::{vergen, Config};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
@@ -22,4 +23,6 @@ async fn main() {
|
||||
// for some strange reason we need to add a leading `/` to the windows path even though it's
|
||||
// not a valid windows path... but hey, it works...
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
|
||||
|
||||
vergen(Config::default()).expect("failed to extract build metadata")
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use crate::commands::*;
|
||||
use crate::config::persistence::pathfinder::GatewayPathfinder;
|
||||
use crate::config::Config;
|
||||
use crate::node::Gateway;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
@@ -76,58 +77,6 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
app
|
||||
}
|
||||
|
||||
fn show_bonding_info(config: &Config) {
|
||||
fn load_sphinx_keys(pathfinder: &GatewayPathfinder) -> encryption::KeyPair {
|
||||
let sphinx_keypair: encryption::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_encryption_key().to_owned(),
|
||||
pathfinder.public_encryption_key().to_owned(),
|
||||
))
|
||||
.expect("Failed to read stored sphinx key files");
|
||||
println!(
|
||||
"Public sphinx key: {}\n",
|
||||
sphinx_keypair.public_key().to_base58_string()
|
||||
);
|
||||
sphinx_keypair
|
||||
}
|
||||
|
||||
fn load_identity_keys(pathfinder: &GatewayPathfinder) -> identity::KeyPair {
|
||||
let identity_keypair: identity::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_identity_key().to_owned(),
|
||||
pathfinder.public_identity_key().to_owned(),
|
||||
))
|
||||
.expect("Failed to read stored identity key files");
|
||||
println!(
|
||||
"Public identity key: {}\n",
|
||||
identity_keypair.public_key().to_base58_string()
|
||||
);
|
||||
identity_keypair
|
||||
}
|
||||
|
||||
let pathfinder = GatewayPathfinder::new_from_config(config);
|
||||
let identity_keypair = load_identity_keys(&pathfinder);
|
||||
let sphinx_keypair = load_sphinx_keys(&pathfinder);
|
||||
|
||||
println!(
|
||||
"\nTo bond your gateway you will [most likely] need to provide the following:
|
||||
Identity key: {}
|
||||
Sphinx key: {}
|
||||
Host: {}
|
||||
Mix Port: {}
|
||||
Clients Port: {}
|
||||
Location: [physical location of your node's server]
|
||||
Version: {}
|
||||
",
|
||||
identity_keypair.public_key().to_base58_string(),
|
||||
sphinx_keypair.public_key().to_base58_string(),
|
||||
config.get_announce_address(),
|
||||
config.get_mix_port(),
|
||||
config.get_clients_port(),
|
||||
config.get_version(),
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn execute(matches: ArgMatches<'static>) {
|
||||
let id = matches.value_of(ID_ARG_NAME).unwrap();
|
||||
println!("Initialising gateway {}...", id);
|
||||
@@ -176,7 +125,7 @@ pub async fn execute(matches: ArgMatches<'static>) {
|
||||
.save_to_file(None)
|
||||
.expect("Failed to save the config file");
|
||||
println!("Saved configuration file to {:?}", config_save_location);
|
||||
|
||||
println!("Gateway configuration completed.\n\n\n");
|
||||
show_bonding_info(&config);
|
||||
|
||||
Gateway::new(config).await.print_node_details()
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use clap::ArgMatches;
|
||||
use url::Url;
|
||||
|
||||
pub(crate) mod init;
|
||||
pub(crate) mod node_details;
|
||||
pub(crate) mod run;
|
||||
pub(crate) mod sign;
|
||||
pub(crate) mod upgrade;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::*;
|
||||
use crate::config::Config;
|
||||
use crate::node::Gateway;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
use log::error;
|
||||
|
||||
pub fn command_args<'a, 'b>() -> App<'a, 'b> {
|
||||
App::new("node-details")
|
||||
.about("Show details of this gateway")
|
||||
.arg(
|
||||
Arg::with_name(ID_ARG_NAME)
|
||||
.long(ID_ARG_NAME)
|
||||
.help("The id of the gateway you want to show details for")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn execute(matches: ArgMatches<'static>) {
|
||||
let id = matches.value_of(ID_ARG_NAME).unwrap();
|
||||
|
||||
let config = match Config::load_from_file(Some(id)) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
Gateway::new(config).await.print_node_details()
|
||||
}
|
||||
@@ -2,12 +2,10 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::*;
|
||||
use crate::config::persistence::pathfinder::GatewayPathfinder;
|
||||
use crate::config::Config;
|
||||
use crate::node::Gateway;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use log::*;
|
||||
use version_checker::is_minor_version_compatible;
|
||||
|
||||
@@ -92,32 +90,6 @@ fn special_addresses() -> Vec<&'static str> {
|
||||
vec!["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"]
|
||||
}
|
||||
|
||||
fn load_sphinx_keys(pathfinder: &GatewayPathfinder) -> encryption::KeyPair {
|
||||
let sphinx_keypair: encryption::KeyPair = pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_encryption_key().to_owned(),
|
||||
pathfinder.public_encryption_key().to_owned(),
|
||||
))
|
||||
.expect("Failed to read stored sphinx key files");
|
||||
println!(
|
||||
"Public sphinx key: {}\n",
|
||||
sphinx_keypair.public_key().to_base58_string()
|
||||
);
|
||||
sphinx_keypair
|
||||
}
|
||||
|
||||
fn load_identity_keys(pathfinder: &GatewayPathfinder) -> identity::KeyPair {
|
||||
let identity_keypair: identity::KeyPair = pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_identity_key().to_owned(),
|
||||
pathfinder.public_identity_key().to_owned(),
|
||||
))
|
||||
.expect("Failed to read stored identity key files");
|
||||
println!(
|
||||
"Public identity key: {}\n",
|
||||
identity_keypair.public_key().to_base58_string()
|
||||
);
|
||||
identity_keypair
|
||||
}
|
||||
|
||||
// this only checks compatibility between config the binary. It does not take into consideration
|
||||
// network version. It might do so in the future.
|
||||
fn version_check(cfg: &Config) -> bool {
|
||||
@@ -157,32 +129,12 @@ pub async fn execute(matches: ArgMatches<'static>) {
|
||||
return;
|
||||
}
|
||||
|
||||
let pathfinder = GatewayPathfinder::new_from_config(&config);
|
||||
let sphinx_keypair = load_sphinx_keys(&pathfinder);
|
||||
let identity = load_identity_keys(&pathfinder);
|
||||
|
||||
if special_addresses().contains(&&*config.get_listening_address().to_string()) {
|
||||
show_binding_warning(config.get_listening_address().to_string());
|
||||
}
|
||||
|
||||
println!(
|
||||
"Validator API servers: {:?}",
|
||||
config.get_validator_api_endpoints()
|
||||
);
|
||||
let mut gateway = Gateway::new(config).await;
|
||||
gateway.print_node_details();
|
||||
|
||||
println!(
|
||||
"Listening for incoming packets on {}",
|
||||
config.get_listening_address()
|
||||
);
|
||||
println!(
|
||||
"Announcing the following address: {}",
|
||||
config.get_announce_address()
|
||||
);
|
||||
|
||||
println!("Data store is at: {:?}", config.get_persistent_store_path());
|
||||
|
||||
Gateway::new(config, sphinx_keypair, identity)
|
||||
.await
|
||||
.run()
|
||||
.await;
|
||||
gateway.run().await;
|
||||
}
|
||||
|
||||
+37
-3
@@ -1,7 +1,7 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{App, ArgMatches};
|
||||
use clap::{crate_version, App, ArgMatches};
|
||||
|
||||
mod commands;
|
||||
mod config;
|
||||
@@ -14,13 +14,15 @@ async fn main() {
|
||||
println!("{}", banner());
|
||||
|
||||
let arg_matches = App::new("Nym Mixnet Gateway")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.version(crate_version!())
|
||||
.long_version(&*long_version())
|
||||
.author("Nymtech")
|
||||
.about("Implementation of the Nym Mixnet Gateway")
|
||||
.subcommand(commands::init::command_args())
|
||||
.subcommand(commands::run::command_args())
|
||||
.subcommand(commands::sign::command_args())
|
||||
.subcommand(commands::upgrade::command_args())
|
||||
.subcommand(commands::node_details::command_args())
|
||||
.get_matches();
|
||||
|
||||
execute(arg_matches).await;
|
||||
@@ -32,6 +34,7 @@ async fn execute(matches: ArgMatches<'static>) {
|
||||
("run", Some(m)) => commands::run::execute(m.clone()).await,
|
||||
("upgrade", Some(m)) => commands::upgrade::execute(m.clone()).await,
|
||||
("sign", Some(m)) => commands::sign::execute(m),
|
||||
("node-details", Some(m)) => commands::node_details::execute(m.clone()).await,
|
||||
_ => println!("{}", usage()),
|
||||
}
|
||||
}
|
||||
@@ -53,7 +56,38 @@ fn banner() -> String {
|
||||
(gateway - version {:})
|
||||
|
||||
"#,
|
||||
env!("CARGO_PKG_VERSION")
|
||||
crate_version!()
|
||||
)
|
||||
}
|
||||
|
||||
fn long_version() -> String {
|
||||
format!(
|
||||
r#"
|
||||
{:<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"),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+62
-17
@@ -15,6 +15,7 @@ use std::net::SocketAddr;
|
||||
use std::process;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::config::persistence::pathfinder::GatewayPathfinder;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use crate::node::client_handling::websocket::connection_handler::eth_events::ERC20Bridge;
|
||||
#[cfg(feature = "coconut")]
|
||||
@@ -29,13 +30,25 @@ pub(crate) mod storage;
|
||||
pub struct Gateway {
|
||||
config: Config,
|
||||
/// ed25519 keypair used to assert one's identity.
|
||||
identity: Arc<identity::KeyPair>,
|
||||
identity_keypair: Arc<identity::KeyPair>,
|
||||
/// x25519 keypair used for Diffie-Hellman. Currently only used for sphinx key derivation.
|
||||
encryption_keys: Arc<encryption::KeyPair>,
|
||||
sphinx_keypair: Arc<encryption::KeyPair>,
|
||||
storage: PersistentStorage,
|
||||
}
|
||||
|
||||
impl Gateway {
|
||||
pub async fn new(config: Config) -> Self {
|
||||
let storage = Self::initialise_storage(&config).await;
|
||||
let pathfinder = GatewayPathfinder::new_from_config(&config);
|
||||
|
||||
Gateway {
|
||||
config,
|
||||
identity_keypair: Arc::new(Self::load_identity_keys(&pathfinder)),
|
||||
sphinx_keypair: Arc::new(Self::load_sphinx_keys(&pathfinder)),
|
||||
storage,
|
||||
}
|
||||
}
|
||||
|
||||
async fn initialise_storage(config: &Config) -> PersistentStorage {
|
||||
let path = config.get_persistent_store_path();
|
||||
let retrieval_limit = config.get_message_retrieval_limit();
|
||||
@@ -45,19 +58,51 @@ impl Gateway {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn new(
|
||||
config: Config,
|
||||
encryption_keys: encryption::KeyPair,
|
||||
identity: identity::KeyPair,
|
||||
) -> Self {
|
||||
let storage = Self::initialise_storage(&config).await;
|
||||
fn load_identity_keys(pathfinder: &GatewayPathfinder) -> identity::KeyPair {
|
||||
let identity_keypair: identity::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_identity_key().to_owned(),
|
||||
pathfinder.public_identity_key().to_owned(),
|
||||
))
|
||||
.expect("Failed to read stored identity key files");
|
||||
identity_keypair
|
||||
}
|
||||
|
||||
Gateway {
|
||||
config,
|
||||
identity: Arc::new(identity),
|
||||
encryption_keys: Arc::new(encryption_keys),
|
||||
storage,
|
||||
}
|
||||
fn load_sphinx_keys(pathfinder: &GatewayPathfinder) -> encryption::KeyPair {
|
||||
let sphinx_keypair: encryption::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_encryption_key().to_owned(),
|
||||
pathfinder.public_encryption_key().to_owned(),
|
||||
))
|
||||
.expect("Failed to read stored sphinx key files");
|
||||
sphinx_keypair
|
||||
}
|
||||
|
||||
pub(crate) fn print_node_details(&self) {
|
||||
println!(
|
||||
"Identity Key: {}",
|
||||
self.identity_keypair.public_key().to_base58_string()
|
||||
);
|
||||
println!(
|
||||
"Sphinx Key: {}",
|
||||
self.sphinx_keypair.public_key().to_base58_string()
|
||||
);
|
||||
println!(
|
||||
"Host: {} (bind address: {})",
|
||||
self.config.get_announce_address(),
|
||||
self.config.get_listening_address()
|
||||
);
|
||||
println!("Version: {}", self.config.get_version());
|
||||
println!(
|
||||
"Mix Port: {}, Clients port: {}",
|
||||
self.config.get_mix_port(),
|
||||
self.config.get_clients_port()
|
||||
);
|
||||
|
||||
println!(
|
||||
"Data store is at: {:?}",
|
||||
self.config.get_persistent_store_path()
|
||||
);
|
||||
}
|
||||
|
||||
fn start_mix_socket_listener(
|
||||
@@ -68,7 +113,7 @@ impl Gateway {
|
||||
info!("Starting mix socket listener...");
|
||||
|
||||
let packet_processor =
|
||||
mixnet_handling::PacketProcessor::new(self.encryption_keys.private_key());
|
||||
mixnet_handling::PacketProcessor::new(self.sphinx_keypair.private_key());
|
||||
|
||||
let connection_handler = ConnectionHandler::new(
|
||||
packet_processor,
|
||||
@@ -101,7 +146,7 @@ impl Gateway {
|
||||
|
||||
websocket::Listener::new(
|
||||
listening_address,
|
||||
Arc::clone(&self.identity),
|
||||
Arc::clone(&self.identity_keypair),
|
||||
#[cfg(feature = "coconut")]
|
||||
verification_key,
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
@@ -168,7 +213,7 @@ impl Gateway {
|
||||
info!("Starting nym gateway!");
|
||||
|
||||
if let Some(duplicate_node_key) = self.check_if_same_ip_gateway_exists().await {
|
||||
if duplicate_node_key == self.identity.public_key().to_base58_string() {
|
||||
if duplicate_node_key == self.identity_keypair.public_key().to_base58_string() {
|
||||
warn!("We seem to have not unregistered after going offline - there's a node with identical identity and announce-host as us registered.")
|
||||
} else {
|
||||
error!(
|
||||
|
||||
@@ -47,3 +47,6 @@ version-checker = { path="../common/version-checker" }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.5"
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use vergen::{vergen, Config};
|
||||
|
||||
fn main() {
|
||||
vergen(Config::default()).expect("failed to extract build metadata")
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
use crate::commands::*;
|
||||
use crate::config::persistence::pathfinder::MixNodePathfinder;
|
||||
use crate::config::Config;
|
||||
use crate::node::MixNode;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
@@ -58,47 +59,6 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
)
|
||||
}
|
||||
|
||||
fn show_bonding_info(config: &Config) {
|
||||
fn load_identity_keys(pathfinder: &MixNodePathfinder) -> identity::KeyPair {
|
||||
let identity_keypair: identity::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_identity_key().to_owned(),
|
||||
pathfinder.public_identity_key().to_owned(),
|
||||
))
|
||||
.expect("Failed to read stored identity key files");
|
||||
identity_keypair
|
||||
}
|
||||
|
||||
fn load_sphinx_keys(pathfinder: &MixNodePathfinder) -> encryption::KeyPair {
|
||||
let sphinx_keypair: encryption::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_encryption_key().to_owned(),
|
||||
pathfinder.public_encryption_key().to_owned(),
|
||||
))
|
||||
.expect("Failed to read stored sphinx key files");
|
||||
sphinx_keypair
|
||||
}
|
||||
|
||||
let pathfinder = MixNodePathfinder::new_from_config(config);
|
||||
let identity_keypair = load_identity_keys(&pathfinder);
|
||||
let sphinx_keypair = load_sphinx_keys(&pathfinder);
|
||||
|
||||
println!(
|
||||
"\nTo bond your mixnode you will need to provide the following:
|
||||
Identity key: {}
|
||||
Sphinx key: {}
|
||||
Address: {}
|
||||
Mix port: {}
|
||||
Version: {}
|
||||
",
|
||||
identity_keypair.public_key().to_base58_string(),
|
||||
sphinx_keypair.public_key().to_base58_string(),
|
||||
config.get_announce_address(),
|
||||
config.get_mix_port(),
|
||||
config.get_version(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn execute(matches: &ArgMatches) {
|
||||
// TODO: this should probably be made implicit by slapping `#[tokio::main]` on our main method
|
||||
// and then removing runtime from mixnode itself in `run`
|
||||
@@ -152,6 +112,6 @@ pub fn execute(matches: &ArgMatches) {
|
||||
println!("Saved configuration file to {:?}", config_save_location);
|
||||
println!("Mixnode configuration completed.\n\n\n");
|
||||
|
||||
show_bonding_info(&config)
|
||||
MixNode::new(config).print_node_details()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use url::Url;
|
||||
|
||||
pub(crate) mod describe;
|
||||
pub(crate) mod init;
|
||||
pub(crate) mod node_details;
|
||||
pub(crate) mod run;
|
||||
pub(crate) mod sign;
|
||||
pub(crate) mod upgrade;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::*;
|
||||
use crate::config::Config;
|
||||
use crate::node::MixNode;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
|
||||
pub fn command_args<'a, 'b>() -> App<'a, 'b> {
|
||||
App::new("node-details")
|
||||
.about("Show details of this mixnode")
|
||||
.arg(
|
||||
Arg::with_name(ID_ARG_NAME)
|
||||
.long(ID_ARG_NAME)
|
||||
.help("The id of the mixnode you want to show details for")
|
||||
.takes_value(true)
|
||||
.required(true),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn execute(matches: &ArgMatches) {
|
||||
let id = matches.value_of(ID_ARG_NAME).unwrap();
|
||||
|
||||
let config = match Config::load_from_file(Some(id)) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
MixNode::new(config).print_node_details()
|
||||
}
|
||||
@@ -2,12 +2,10 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::*;
|
||||
use crate::config::{persistence::pathfinder::MixNodePathfinder, Config};
|
||||
use crate::node::node_description::NodeDescription;
|
||||
use crate::config::Config;
|
||||
use crate::node::MixNode;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use log::warn;
|
||||
use version_checker::is_minor_version_compatible;
|
||||
|
||||
@@ -75,24 +73,6 @@ fn special_addresses() -> Vec<&'static str> {
|
||||
vec!["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"]
|
||||
}
|
||||
|
||||
fn load_identity_keys(pathfinder: &MixNodePathfinder) -> identity::KeyPair {
|
||||
let identity_keypair: identity::KeyPair = pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_identity_key().to_owned(),
|
||||
pathfinder.public_identity_key().to_owned(),
|
||||
))
|
||||
.expect("Failed to read stored identity key files");
|
||||
identity_keypair
|
||||
}
|
||||
|
||||
fn load_sphinx_keys(pathfinder: &MixNodePathfinder) -> encryption::KeyPair {
|
||||
let sphinx_keypair: encryption::KeyPair = pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_encryption_key().to_owned(),
|
||||
pathfinder.public_encryption_key().to_owned(),
|
||||
))
|
||||
.expect("Failed to read stored sphinx key files");
|
||||
sphinx_keypair
|
||||
}
|
||||
|
||||
// this only checks compatibility between config the binary. It does not take into consideration
|
||||
// network version. It might do so in the future.
|
||||
fn version_check(cfg: &Config) -> bool {
|
||||
@@ -132,41 +112,15 @@ pub fn execute(matches: &ArgMatches) {
|
||||
return;
|
||||
}
|
||||
|
||||
let pathfinder = MixNodePathfinder::new_from_config(&config);
|
||||
let identity_keypair = load_identity_keys(&pathfinder);
|
||||
let sphinx_keypair = load_sphinx_keys(&pathfinder);
|
||||
|
||||
if special_addresses().contains(&&*config.get_listening_address().to_string()) {
|
||||
show_binding_warning(config.get_listening_address().to_string());
|
||||
}
|
||||
|
||||
let description = NodeDescription::load_from_file(Config::default_config_directory(Some(id)))
|
||||
.unwrap_or_default();
|
||||
let mut mixnode = MixNode::new(config);
|
||||
|
||||
println!(
|
||||
"Validator servers: {:?}",
|
||||
config.get_validator_api_endpoints()
|
||||
);
|
||||
println!(
|
||||
"Listening for incoming packets on {}",
|
||||
config.get_listening_address()
|
||||
);
|
||||
println!(
|
||||
"Announcing the following address: {}",
|
||||
config.get_announce_address()
|
||||
);
|
||||
"\nTo bond your mixnode, go to https://testnet-milhon-wallet.nymtech.net/. You will need to provide the following:");
|
||||
mixnode.print_node_details();
|
||||
|
||||
println!(
|
||||
"\nTo bond your mixnode, go to https://testnet-milhon-wallet.nymtech.net/. You will need to provide the following:
|
||||
Identity key: {}
|
||||
Sphinx key: {}
|
||||
Address: {}
|
||||
Version: {}
|
||||
",
|
||||
identity_keypair.public_key().to_base58_string(),
|
||||
sphinx_keypair.public_key().to_base58_string(),
|
||||
config.get_announce_address(),
|
||||
config.get_version(),
|
||||
);
|
||||
MixNode::new(config, description, identity_keypair, sphinx_keypair).run();
|
||||
mixnode.run()
|
||||
}
|
||||
|
||||
@@ -197,6 +197,10 @@ impl Config {
|
||||
}
|
||||
|
||||
// getters
|
||||
pub fn get_id(&self) -> String {
|
||||
self.mixnode.id.clone()
|
||||
}
|
||||
|
||||
pub fn get_config_file_save_location(&self) -> PathBuf {
|
||||
self.config_directory().join(Self::config_file_name())
|
||||
}
|
||||
|
||||
+37
-3
@@ -4,7 +4,7 @@ extern crate rocket;
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{App, ArgMatches};
|
||||
use clap::{crate_version, App, ArgMatches};
|
||||
|
||||
mod commands;
|
||||
mod config;
|
||||
@@ -16,7 +16,8 @@ fn main() {
|
||||
println!("{}", banner());
|
||||
|
||||
let arg_matches = App::new("Nym Mixnode")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.version(crate_version!())
|
||||
.long_version(&*long_version())
|
||||
.author("Nymtech")
|
||||
.about("Implementation of a Loopix-based Mixnode")
|
||||
.subcommand(commands::describe::command_args())
|
||||
@@ -24,6 +25,7 @@ fn main() {
|
||||
.subcommand(commands::run::command_args())
|
||||
.subcommand(commands::upgrade::command_args())
|
||||
.subcommand(commands::sign::command_args())
|
||||
.subcommand(commands::node_details::command_args())
|
||||
.get_matches();
|
||||
|
||||
execute(arg_matches);
|
||||
@@ -36,6 +38,7 @@ fn execute(matches: ArgMatches) {
|
||||
("run", Some(m)) => commands::run::execute(m),
|
||||
("sign", Some(m)) => commands::sign::execute(m),
|
||||
("upgrade", Some(m)) => commands::upgrade::execute(m),
|
||||
("node-details", Some(m)) => commands::node_details::execute(m),
|
||||
_ => println!("{}", usage()),
|
||||
}
|
||||
}
|
||||
@@ -57,7 +60,38 @@ fn banner() -> String {
|
||||
(mixnode - version {:})
|
||||
|
||||
"#,
|
||||
env!("CARGO_PKG_VERSION")
|
||||
crate_version!()
|
||||
)
|
||||
}
|
||||
|
||||
fn long_version() -> String {
|
||||
format!(
|
||||
r#"
|
||||
{:<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"),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+56
-9
@@ -1,6 +1,7 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::persistence::pathfinder::MixNodePathfinder;
|
||||
use crate::config::Config;
|
||||
use crate::node::http::{
|
||||
description::description,
|
||||
@@ -14,6 +15,7 @@ use crate::node::listener::Listener;
|
||||
use crate::node::node_description::NodeDescription;
|
||||
use crate::node::node_statistics::NodeStatsWrapper;
|
||||
use crate::node::packet_delayforwarder::{DelayForwarder, PacketDelayForwardSender};
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use log::{error, info, warn};
|
||||
use mixnode_common::verloc::{self, AtomicVerlocResult, VerlocMeasurer};
|
||||
@@ -41,20 +43,65 @@ pub struct MixNode {
|
||||
}
|
||||
|
||||
impl MixNode {
|
||||
pub fn new(
|
||||
config: Config,
|
||||
descriptor: NodeDescription,
|
||||
identity_keypair: identity::KeyPair,
|
||||
sphinx_keypair: encryption::KeyPair,
|
||||
) -> Self {
|
||||
pub fn new(config: Config) -> Self {
|
||||
let pathfinder = MixNodePathfinder::new_from_config(&config);
|
||||
|
||||
MixNode {
|
||||
descriptor: Self::load_node_description(&config),
|
||||
identity_keypair: Arc::new(Self::load_identity_keys(&pathfinder)),
|
||||
sphinx_keypair: Arc::new(Self::load_sphinx_keys(&pathfinder)),
|
||||
config,
|
||||
descriptor,
|
||||
identity_keypair: Arc::new(identity_keypair),
|
||||
sphinx_keypair: Arc::new(sphinx_keypair),
|
||||
}
|
||||
}
|
||||
|
||||
fn load_node_description(config: &Config) -> NodeDescription {
|
||||
NodeDescription::load_from_file(Config::default_config_directory(Some(&config.get_id())))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn load_identity_keys(pathfinder: &MixNodePathfinder) -> identity::KeyPair {
|
||||
let identity_keypair: identity::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_identity_key().to_owned(),
|
||||
pathfinder.public_identity_key().to_owned(),
|
||||
))
|
||||
.expect("Failed to read stored identity key files");
|
||||
identity_keypair
|
||||
}
|
||||
|
||||
fn load_sphinx_keys(pathfinder: &MixNodePathfinder) -> encryption::KeyPair {
|
||||
let sphinx_keypair: encryption::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_encryption_key().to_owned(),
|
||||
pathfinder.public_encryption_key().to_owned(),
|
||||
))
|
||||
.expect("Failed to read stored sphinx key files");
|
||||
sphinx_keypair
|
||||
}
|
||||
|
||||
pub(crate) fn print_node_details(&self) {
|
||||
println!(
|
||||
"Identity Key: {}",
|
||||
self.identity_keypair.public_key().to_base58_string()
|
||||
);
|
||||
println!(
|
||||
"Sphinx Key: {}",
|
||||
self.sphinx_keypair.public_key().to_base58_string()
|
||||
);
|
||||
println!(
|
||||
"Host: {} (bind address: {})",
|
||||
self.config.get_announce_address(),
|
||||
self.config.get_listening_address()
|
||||
);
|
||||
println!("Version: {}", self.config.get_version());
|
||||
println!(
|
||||
"Mix Port: {}, Verloc port: {}, Http Port: {}",
|
||||
self.config.get_mix_port(),
|
||||
self.config.get_version(),
|
||||
self.config.get_http_api_port()
|
||||
);
|
||||
}
|
||||
|
||||
fn start_http_api(
|
||||
&self,
|
||||
atomic_verloc_result: AtomicVerlocResult,
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
MAJOR_CURRENCY=punk
|
||||
MINOR_CURRENCY=upunk
|
||||
ADMIN_ADDRESS=
|
||||
NETWORK_NAME=testnet-milhon
|
||||
Generated
+147
-144
@@ -57,9 +57,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.45"
|
||||
version = "1.0.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee10e43ae4a853c0a3591d4e2ada1719e553be18199d9da9d4a83f5927c2f5c7"
|
||||
checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
@@ -89,9 +89,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.51"
|
||||
version = "0.1.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e"
|
||||
checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -154,9 +154,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "az"
|
||||
version = "1.1.2"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d6dff4a1892b54d70af377bf7a17064192e822865791d812957f21e3108c325"
|
||||
checksum = "f771a5d1f5503f7f4279a30f3643d3421ba149848b89ecaaec0ea2acf04a5ac4"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
@@ -331,9 +331,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.7.2"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b"
|
||||
checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
@@ -394,9 +394,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.71"
|
||||
version = "1.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd"
|
||||
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
]
|
||||
@@ -448,19 +448,6 @@ dependencies = [
|
||||
"keystream",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.3.0"
|
||||
@@ -696,9 +683,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-crypto"
|
||||
version = "1.0.0-beta2"
|
||||
version = "1.0.0-beta3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c16b255449b3f5cd7fa4b79acd5225b5185655261087a3d8aaac44f88a0e23e9"
|
||||
checksum = "a380b87642204557629c9b72988c47b55fbfe6d474960adba56b22331504956a"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"ed25519-zebra",
|
||||
@@ -709,18 +696,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-derive"
|
||||
version = "1.0.0-beta2"
|
||||
version = "1.0.0-beta3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abad1a6ff427a2f66890a4dce6354b4563cd07cee91a942300e011c921c09ed2"
|
||||
checksum = "866713b2fe13f23038c7d8824c3059d1f28dd94685fb406d1533c4eeeefeefae"
|
||||
dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "1.0.0-beta2"
|
||||
version = "1.0.0-beta3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1660ee3d5734672e1eb4f0ceda403e2d83345e15143a48845f340f3252ce99a6"
|
||||
checksum = "8dbb9939b31441dfa9af3ec9740c8a24d585688401eff1b6b386abb7ad0d10a8"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cosmwasm-crypto",
|
||||
@@ -743,9 +730,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.2.1"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
|
||||
checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
@@ -872,7 +859,7 @@ checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a"
|
||||
dependencies = [
|
||||
"cssparser-macros",
|
||||
"dtoa-short",
|
||||
"itoa",
|
||||
"itoa 0.4.8",
|
||||
"matches",
|
||||
"phf 0.8.0",
|
||||
"proc-macro2",
|
||||
@@ -909,6 +896,12 @@ dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cty"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "3.2.0"
|
||||
@@ -990,9 +983,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.4.4"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28e98c534e9c8a0483aa01d6f6913bc063de254311bd267c9cf535e9b70e15b2"
|
||||
checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
]
|
||||
@@ -1010,14 +1003,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.16"
|
||||
version = "0.99.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df"
|
||||
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"rustc_version 0.4.0",
|
||||
"syn",
|
||||
]
|
||||
|
||||
@@ -1160,9 +1153,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ed25519"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4620d40f6d2601794401d6dd95a5cf69b6c157852539470eeda433a99b3c0efc"
|
||||
checksum = "74e1069e39f1454367eb2de793ed062fac4c35c2934b76a81d90dd9abcd28816"
|
||||
dependencies = [
|
||||
"signature",
|
||||
]
|
||||
@@ -1225,9 +1218,9 @@ checksum = "53dd2e43a7d32952a6054141ee0d75183958620e84e5eab045de362dff13dc99"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.29"
|
||||
version = "0.8.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746"
|
||||
checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
@@ -1286,7 +1279,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92"
|
||||
dependencies = [
|
||||
"memoffset",
|
||||
"rustc_version",
|
||||
"rustc_version 0.3.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1303,9 +1296,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "fixed"
|
||||
version = "1.10.0"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d333a26ec13a023c6dff4b7584de4d323cfee2e508f5dd2bbee6669e4f7efdf"
|
||||
checksum = "80a9a8cb2e34880a498f09367089339bda5e12d6f871640f947850f7113058c0"
|
||||
dependencies = [
|
||||
"az",
|
||||
"bytemuck",
|
||||
@@ -1631,9 +1624,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getset"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24b328c01a4d71d2d8173daa93562a73ab0fe85616876f02500f53d82948c504"
|
||||
checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9"
|
||||
dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
@@ -1843,9 +1836,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.7"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fd819562fcebdac5afc5c113c3ec36f902840b70fd4fc458799c8ce4607ae55"
|
||||
checksum = "8f072413d126e57991455e0a922b31e4c8ba7c2ffbebf6b78b4f8521397d65cd"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
@@ -1937,9 +1930,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hex-literal"
|
||||
version = "0.3.3"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21e4590e13640f19f249fe3e4eca5113bc4289f2497710378190e7f4bd96f45b"
|
||||
checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0"
|
||||
|
||||
[[package]]
|
||||
name = "hkd32"
|
||||
@@ -1997,7 +1990,7 @@ checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
"itoa 0.4.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2025,9 +2018,9 @@ checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440"
|
||||
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
@@ -2047,9 +2040,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.14"
|
||||
version = "0.14.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b91bb1f221b6ea1f1e4371216b70f40748774c2fb5971b450c07773fb92d26b"
|
||||
checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
@@ -2060,7 +2053,7 @@ dependencies = [
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"itoa 0.4.8",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
@@ -2227,9 +2220,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.1"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
|
||||
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
@@ -2240,6 +2233,12 @@ version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||
|
||||
[[package]]
|
||||
name = "javascriptcore-rs"
|
||||
version = "0.14.0"
|
||||
@@ -2328,9 +2327,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.107"
|
||||
version = "0.2.112"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
|
||||
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
@@ -2370,9 +2369,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "loom"
|
||||
version = "0.5.2"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2b9df80a3804094bf49bb29881d18f6f05048db72127e84e09c26fc7c2324f5"
|
||||
checksum = "edc5c7d328e32cc4954e8e01193d7f0ef5ab257b5090b70a964e099a36034309"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"generator",
|
||||
@@ -2420,9 +2419,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.0.1"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
@@ -2447,9 +2446,9 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.4"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
]
|
||||
@@ -2577,9 +2576,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ndk-sys"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d"
|
||||
checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121"
|
||||
|
||||
[[package]]
|
||||
name = "network-defaults"
|
||||
@@ -2587,7 +2586,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-literal",
|
||||
"serde",
|
||||
"time 0.3.4",
|
||||
"time 0.3.5",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -2780,9 +2779,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.8.0"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
@@ -2798,9 +2797,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
version = "2.0.1"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b46b233de7d83bc167fe43ae2dda3b5b84e80e09cceba581e4decb958a4896bf"
|
||||
checksum = "176ee4b630d174d2da8241336763bb459281dddc0f4d87f72c3b1efc9a6109b7"
|
||||
dependencies = [
|
||||
"pathdiff",
|
||||
"winapi",
|
||||
@@ -2828,9 +2827,9 @@ checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.70"
|
||||
version = "0.9.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6517987b3f8226b5da3661dad65ff7f300cc59fb5ea8333ca191fc65fde3edf"
|
||||
checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"cc",
|
||||
@@ -3041,9 +3040,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.10.0"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9fc3db1018c4b59d7d582a739436478b6035138b6aecbce989fc91c3e98409f"
|
||||
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
|
||||
dependencies = [
|
||||
"phf_macros 0.10.0",
|
||||
"phf_shared 0.10.0",
|
||||
@@ -3170,9 +3169,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.22"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f"
|
||||
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
|
||||
|
||||
[[package]]
|
||||
name = "pmutil"
|
||||
@@ -3278,9 +3277,9 @@ checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.32"
|
||||
version = "1.0.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
|
||||
checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
@@ -3542,11 +3541,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.3.3"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a441a7a6c80ad6473bd4b74ec1c9a4c951794285bf941c2126f607c72e48211"
|
||||
checksum = "e28f55143d0548dad60bb4fbdc835a3d7ac6acc3324506450c5fdd6e42903a76"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"raw-window-handle 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fba75eee94a9d5273a68c9e1e105d9cffe1ef700532325788389e5a83e2522b7"
|
||||
dependencies = [
|
||||
"cty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3639,9 +3648,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.6"
|
||||
version = "0.11.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d2927ca2f685faf0fc620ac4834690d29e7abb153add10f5812eef20b5e280"
|
||||
checksum = "07bea77bc708afa10e59905c3d4af7c8fd43c9214251673095ff8b14345fcbc5"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
@@ -3688,7 +3697,7 @@ dependencies = [
|
||||
"objc",
|
||||
"objc-foundation",
|
||||
"objc_id",
|
||||
"raw-window-handle",
|
||||
"raw-window-handle 0.3.4",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
@@ -3730,6 +3739,15 @@ dependencies = [
|
||||
"semver 0.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||
dependencies = [
|
||||
"semver 1.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.19.1"
|
||||
@@ -3757,15 +3775,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.5"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
|
||||
checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
@@ -3788,9 +3806,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.6"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7a48d098c2a7fdf5740b19deb1181b4fb8a9e68e03ae517c14cde04b5725409"
|
||||
checksum = "c6b5a3c80cea1ab61f4260238409510e814e38b4b563c06044edf91e7dc070e3"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"schemars_derive",
|
||||
@@ -3800,9 +3818,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.6"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a9ea2a613fe4cd7118b2bb101a25d8ae6192e1975179b67b2f17afd11e70ac8"
|
||||
checksum = "41ae4dce13e8614c46ac3c38ef1c0d668b101df6ac39817aebdaa26642ddae9b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3901,18 +3919,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.130"
|
||||
version = "1.0.131"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
|
||||
checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-json-wasm"
|
||||
version = "0.3.1"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50eef3672ec8fa45f3457fd423ba131117786784a895548021976117c1ded449"
|
||||
checksum = "042ac496d97e5885149d34139bad1d617192770d7eb8f1866da2317ff4501853"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -3928,9 +3946,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.130"
|
||||
version = "1.0.131"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
|
||||
checksum = "b710a83c4e0dff6a3d511946b95274ad9ca9e5d3ae497b63fda866ac955358d2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3950,11 +3968,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.69"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e466864e431129c7e0d3476b92f20458e5879919a0596c6472738d9fa2d342f8"
|
||||
checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"itoa 1.0.1",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
@@ -3977,7 +3995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97"
|
||||
dependencies = [
|
||||
"dtoa",
|
||||
"itoa",
|
||||
"itoa 0.4.8",
|
||||
"serde",
|
||||
"url",
|
||||
]
|
||||
@@ -3989,7 +4007,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"itoa 0.4.8",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
@@ -4511,7 +4529,7 @@ dependencies = [
|
||||
"ndk-sys",
|
||||
"objc",
|
||||
"parking_lot",
|
||||
"raw-window-handle",
|
||||
"raw-window-handle 0.3.4",
|
||||
"scopeguard",
|
||||
"serde",
|
||||
"unicode-segmentation",
|
||||
@@ -4521,9 +4539,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tar"
|
||||
version = "0.4.37"
|
||||
version = "0.4.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f5515d3add52e0bbdcad7b83c388bb36ba7b754dda3b5f5bc2d38640cdba5c"
|
||||
checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"libc",
|
||||
@@ -4553,7 +4571,7 @@ dependencies = [
|
||||
"open",
|
||||
"percent-encoding",
|
||||
"rand 0.8.4",
|
||||
"raw-window-handle",
|
||||
"raw-window-handle 0.3.4",
|
||||
"rfd",
|
||||
"semver 1.0.4",
|
||||
"serde",
|
||||
@@ -4661,7 +4679,7 @@ checksum = "fcb9b79594f22b6ed0cc8362e0dfde5b7969962de3cd8ca683de702e59e8221b"
|
||||
dependencies = [
|
||||
"html5ever",
|
||||
"kuchiki",
|
||||
"phf 0.10.0",
|
||||
"phf 0.10.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
@@ -4687,13 +4705,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tendermint"
|
||||
version = "0.23.0"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50d1cdb0236becb17ab35a2ed1566503e412fd910944dc940239857bb7663652"
|
||||
checksum = "9015fdeab074f9b8f97dcb89c2bb2ec8537c89423e95551e8d7ecdfbab58a329"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"ed25519",
|
||||
"ed25519-dalek",
|
||||
"flex-error",
|
||||
@@ -4713,14 +4730,15 @@ dependencies = [
|
||||
"subtle 2.4.1",
|
||||
"subtle-encoding",
|
||||
"tendermint-proto",
|
||||
"time 0.3.5",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tendermint-config"
|
||||
version = "0.23.0"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a1f94250d30e3011130a09756b05985d8dbfbd562cf261b5a17e36d89a37992"
|
||||
checksum = "a2b2e6d4442bab49319dbacdfd79c5929bc6e0b35d1e0d959ff5b79fddf3f018"
|
||||
dependencies = [
|
||||
"flex-error",
|
||||
"serde",
|
||||
@@ -4732,12 +4750,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tendermint-proto"
|
||||
version = "0.23.0"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff16a7b42bdbcf31c8cd10a4cffc7631f2a301360ba3a3f71dde48eabfa5bced"
|
||||
checksum = "da86f6e52ced9c2f24c4ae57662ce8a44dd90ee7bc47bae27a710b02d48e193c"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"chrono",
|
||||
"flex-error",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
@@ -4746,17 +4763,17 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"subtle-encoding",
|
||||
"time 0.3.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tendermint-rpc"
|
||||
version = "0.23.0"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7842dcd5edb60b077572aa92ff8b29fc810b9b463310f9810a2607474130db4"
|
||||
checksum = "50f5f4875c36798e5590894a5176cf8d75e4bc81ec34ced1b9a87609e7d56861"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"flex-error",
|
||||
"futures",
|
||||
"getrandom 0.1.16",
|
||||
@@ -4774,6 +4791,7 @@ dependencies = [
|
||||
"tendermint-config",
|
||||
"tendermint-proto",
|
||||
"thiserror",
|
||||
"time 0.3.5",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
@@ -4839,9 +4857,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.4"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99beeb0daeac2bd1e86ac2c21caddecb244b39a093594da1a661ec2060c7aedd"
|
||||
checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"time-macros",
|
||||
@@ -4855,11 +4873,10 @@ checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.13.0"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "588b2d10a336da58d877567cd8fb8a14b463e2104910f8132cd054b4b96e29ee"
|
||||
checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"bytes",
|
||||
"libc",
|
||||
"memchr",
|
||||
@@ -4872,9 +4889,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.5.1"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "114383b041aa6212c579467afa0075fbbdd0718de036100bc0ba7961d8cb9095"
|
||||
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4974,36 +4991,22 @@ dependencies = [
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-serde"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.2.25"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71"
|
||||
checksum = "245da694cc7fc4729f3f418b304cb57789f1bed2a78c575407ab8a23f53cb4d3"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
"matchers",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sharded-slab",
|
||||
"smallvec 1.7.0",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
"tracing-serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export const config = {
|
||||
MAJOR_CURRENCY: process.env.MAJOR_CURRENCY,
|
||||
MINOR_CURRENCY: process.env.MINOR_CURRENCY,
|
||||
ADMIN_ADDRESS: process.env.ADMIN_ADDRESS,
|
||||
NETWORK_NAME: process.env.NETWORK_NAME,
|
||||
}
|
||||
@@ -4,8 +4,8 @@
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"webpack:dev": "yarn webpack serve",
|
||||
"webpack:prod": "yarn webpack --progress --config webpack.prod.config.js",
|
||||
"webpack:dev": "yarn webpack serve --config webpack.dev.js",
|
||||
"webpack:prod": "yarn webpack --progress --config webpack.prod.js",
|
||||
"tauri:dev": "yarn tauri dev",
|
||||
"tauri:build": "yarn tauri build",
|
||||
"dev": "yarn run webpack:dev & yarn run tauri:dev",
|
||||
@@ -45,6 +45,7 @@
|
||||
"@types/semver": "^7.3.8",
|
||||
"babel-loader": "^8.2.2",
|
||||
"css-loader": "^6.2.0",
|
||||
"dotenv-webpack": "^7.0.3",
|
||||
"favicons": "^6.2.2",
|
||||
"favicons-webpack-plugin": "^5.0.2",
|
||||
"file-loader": "^6.2.0",
|
||||
@@ -54,6 +55,7 @@
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.64.3",
|
||||
"webpack-cli": "^4.8.0",
|
||||
"webpack-dev-server": "^4.5.0"
|
||||
"webpack-dev-server": "^4.5.0",
|
||||
"webpack-merge": "^5.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ fn main() {
|
||||
mixnet::bond::bond_mixnode,
|
||||
mixnet::bond::unbond_gateway,
|
||||
mixnet::bond::unbond_mixnode,
|
||||
mixnet::bond::mixnode_bond_details,
|
||||
mixnet::bond::gateway_bond_details,
|
||||
mixnet::delegate::delegate_to_mixnode,
|
||||
mixnet::delegate::get_reverse_mix_delegations_paged,
|
||||
mixnet::delegate::undelegate_from_mixnode,
|
||||
|
||||
@@ -58,7 +58,10 @@ pub async fn connect_with_mnemonic(
|
||||
pub async fn get_balance(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Balance, BackendError> {
|
||||
match client!(state).get_balance(client!(state).address()).await {
|
||||
match client!(state)
|
||||
.get_mixnet_balance(client!(state).address())
|
||||
.await
|
||||
{
|
||||
Ok(Some(coin)) => {
|
||||
let coin = Coin::new(
|
||||
&coin.amount.to_string(),
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::coin::Coin;
|
||||
use crate::error::BackendError;
|
||||
use crate::state::State;
|
||||
use crate::{Gateway, MixNode};
|
||||
use mixnet_contract::{GatewayBond, MixNodeBond};
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
@@ -48,3 +49,23 @@ pub async fn bond_mixnode(
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn mixnode_bond_details(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Option<MixNodeBond>, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let client = guard.client()?;
|
||||
let bond = client.owns_mixnode(client.address()).await?;
|
||||
Ok(bond)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn gateway_bond_details(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Option<GatewayBond>, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let client = guard.client()?;
|
||||
let bond = client.owns_gateway(client.address()).await?;
|
||||
Ok(bond)
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ import React, { createContext, useEffect, useState } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { TClientDetails, TSignInWithMnemonic } from '../types'
|
||||
import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance'
|
||||
import { config } from '../../config'
|
||||
|
||||
export const { MAJOR_CURRENCY, MINOR_CURRENCY, ADMIN_ADDRESS, NETWORK_NAME } = config
|
||||
|
||||
export const ADMIN_ADDRESS = 'punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0'
|
||||
export const urls = {
|
||||
blockExplorer: 'https://testnet-milhon-blocks.nymtech.net',
|
||||
blockExplorer: `https://${NETWORK_NAME}-blocks.nymtech.net`,
|
||||
}
|
||||
|
||||
type TClientContext = {
|
||||
|
||||
@@ -7,26 +7,21 @@ import {
|
||||
Gateway,
|
||||
MixNode,
|
||||
Operation,
|
||||
TauriContractSettingsParams,
|
||||
TauriContractStateParams,
|
||||
TauriTxResult,
|
||||
TCreateAccount,
|
||||
TDelegation,
|
||||
TSignInWithMnemonic,
|
||||
} from '../types'
|
||||
|
||||
export const createAccount = async (): Promise<TCreateAccount> =>
|
||||
await invoke('create_new_account')
|
||||
export const createAccount = async (): Promise<TCreateAccount> => await invoke('create_new_account')
|
||||
|
||||
export const signInWithMnemonic = async (
|
||||
mnemonic: string,
|
||||
): Promise<TSignInWithMnemonic> =>
|
||||
export const signInWithMnemonic = async (mnemonic: string): Promise<TSignInWithMnemonic> =>
|
||||
await invoke('connect_with_mnemonic', { mnemonic })
|
||||
|
||||
export const minorToMajor = async (amount: string): Promise<Coin> =>
|
||||
await invoke('minor_to_major', { amount })
|
||||
export const minorToMajor = async (amount: string): Promise<Coin> => await invoke('minor_to_major', { amount })
|
||||
|
||||
export const majorToMinor = async (amount: string): Promise<Coin> =>
|
||||
await invoke('major_to_minor', { amount })
|
||||
export const majorToMinor = async (amount: string): Promise<Coin> => await invoke('major_to_minor', { amount })
|
||||
|
||||
// NOTE: this uses OUTDATED defaults that might have no resemblance with the reality
|
||||
// as for the actual transaction, the gas cost is being simulated beforehand
|
||||
@@ -41,8 +36,7 @@ export const delegate = async ({
|
||||
type: EnumNodeType
|
||||
identity: string
|
||||
amount: Coin
|
||||
}): Promise<DelegationResult> =>
|
||||
await invoke(`delegate_to_${type}`, { identity, amount })
|
||||
}): Promise<DelegationResult> => await invoke(`delegate_to_${type}`, { identity, amount })
|
||||
|
||||
export const undelegate = async ({
|
||||
type,
|
||||
@@ -50,43 +44,33 @@ export const undelegate = async ({
|
||||
}: {
|
||||
type: EnumNodeType
|
||||
identity: string
|
||||
}): Promise<DelegationResult> =>
|
||||
await invoke(`undelegate_from_${type}`, { identity })
|
||||
}): Promise<DelegationResult> => await invoke(`undelegate_from_${type}`, { identity })
|
||||
|
||||
export const send = async (args: {
|
||||
amount: Coin
|
||||
address: string
|
||||
memo: string
|
||||
}): Promise<TauriTxResult> => await invoke('send', args)
|
||||
export const checkMixnodeOwnership = async (): Promise<boolean> =>
|
||||
await invoke('owns_mixnode')
|
||||
export const send = async (args: { amount: Coin; address: string; memo: string }): Promise<TauriTxResult> =>
|
||||
await invoke('send', args)
|
||||
export const checkMixnodeOwnership = async (): Promise<boolean> => await invoke('owns_mixnode')
|
||||
|
||||
export const checkGatewayOwnership = async (): Promise<boolean> =>
|
||||
await invoke('owns_gateway')
|
||||
export const checkGatewayOwnership = async (): Promise<boolean> => await invoke('owns_gateway')
|
||||
|
||||
export const bond = async ({
|
||||
type,
|
||||
data,
|
||||
amount,
|
||||
pledge,
|
||||
ownerSignature,
|
||||
}: {
|
||||
type: EnumNodeType
|
||||
data: MixNode | Gateway
|
||||
amount: Coin
|
||||
}): Promise<any> => await invoke(`bond_${type}`, { [type]: data, bond: amount })
|
||||
pledge: Coin
|
||||
ownerSignature: string
|
||||
}): Promise<any> => await invoke(`bond_${type}`, { [type]: data, ownerSignature, pledge })
|
||||
|
||||
export const unbond = async (type: EnumNodeType) =>
|
||||
await invoke(`unbond_${type}`)
|
||||
export const unbond = async (type: EnumNodeType) => await invoke(`unbond_${type}`)
|
||||
|
||||
export const userBalance = async (): Promise<Balance> =>
|
||||
await invoke('get_balance')
|
||||
export const userBalance = async (): Promise<Balance> => await invoke('get_balance')
|
||||
|
||||
export const getContractParams =
|
||||
async (): Promise<TauriContractSettingsParams> =>
|
||||
await invoke('get_contract_settings')
|
||||
export const getContractParams = async (): Promise<TauriContractStateParams> => await invoke('get_contract_settings')
|
||||
|
||||
export const setContractParams = async (
|
||||
params: TauriContractSettingsParams,
|
||||
): Promise<TauriContractSettingsParams> =>
|
||||
export const setContractParams = async (params: TauriContractStateParams): Promise<TauriContractStateParams> =>
|
||||
await invoke('update_contract_settings', { params })
|
||||
|
||||
export const getReverseMixDelegations = async (): Promise<TDelegation> =>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useContext } from 'react'
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
@@ -19,15 +18,16 @@ import { NodeTypeSelector } from '../../components/NodeTypeSelector'
|
||||
import { bond, majorToMinor } from '../../requests'
|
||||
import { validationSchema } from './validationSchema'
|
||||
import { Coin, Gateway, MixNode } from '../../types'
|
||||
import { ClientContext } from '../../context/main'
|
||||
import { ClientContext, MAJOR_CURRENCY } from '../../context/main'
|
||||
import { checkHasEnoughFunds } from '../../utils'
|
||||
|
||||
type TBondFormFields = {
|
||||
withAdvancedOptions: boolean
|
||||
nodeType: EnumNodeType
|
||||
ownerSignature: string,
|
||||
ownerSignature: string
|
||||
identityKey: string
|
||||
sphinxKey: string
|
||||
profitMarginPercent: number
|
||||
amount: string
|
||||
host: string
|
||||
version: string
|
||||
@@ -43,9 +43,11 @@ const defaultValues = {
|
||||
nodeType: EnumNodeType.mixnode,
|
||||
identityKey: '',
|
||||
sphinxKey: '',
|
||||
ownerSignature: '',
|
||||
amount: '',
|
||||
host: '',
|
||||
version: '',
|
||||
profitMarginPercent: 10,
|
||||
location: undefined,
|
||||
mixPort: 1789,
|
||||
verlocPort: 1790,
|
||||
@@ -60,6 +62,7 @@ const formatData = (data: TBondFormFields) => {
|
||||
host: data.host,
|
||||
version: data.version,
|
||||
mix_port: data.mixPort,
|
||||
profit_margin_percent: data.profitMarginPercent,
|
||||
}
|
||||
|
||||
if (data.nodeType === EnumNodeType.mixnode) {
|
||||
@@ -108,9 +111,9 @@ export const BondForm = ({
|
||||
}
|
||||
|
||||
const formattedData = formatData(data)
|
||||
const amount = await majorToMinor(data.amount)
|
||||
const pledge = await majorToMinor(data.amount)
|
||||
|
||||
await bond({ type: data.nodeType, data: formattedData, amount })
|
||||
await bond({ type: data.nodeType, ownerSignature: data.ownerSignature, data: formattedData, pledge })
|
||||
.then(() => {
|
||||
userBalance.fetchBalance()
|
||||
onSuccess({ address: data.identityKey, amount: data.amount })
|
||||
@@ -164,7 +167,23 @@ export const BondForm = ({
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={9}>
|
||||
|
||||
<Grid item xs={12} sm={12}>
|
||||
<TextField
|
||||
{...register('ownerSignature')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="ownerSignature"
|
||||
name="ownerSignature"
|
||||
label="Signature on your address"
|
||||
fullWidth
|
||||
error={!!errors.ownerSignature}
|
||||
helperText={errors.ownerSignature?.message}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
{...register('amount')}
|
||||
variant="outlined"
|
||||
@@ -176,12 +195,27 @@ export const BondForm = ({
|
||||
error={!!errors.amount}
|
||||
helperText={errors.amount?.message}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">punk</InputAdornment>,
|
||||
endAdornment: <InputAdornment position="end">{MAJOR_CURRENCY}</InputAdornment>,
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
{...register('profitMarginPercent')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="profitMarginPercent"
|
||||
name="profitMarginPercent"
|
||||
label="Profit percentage"
|
||||
fullWidth
|
||||
error={!!errors.profitMarginPercent}
|
||||
helperText={errors.profitMarginPercent?.message}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
{...register('host')}
|
||||
@@ -230,22 +264,6 @@ export const BondForm = ({
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
|
||||
<Grid item xs={12} sm={12}>
|
||||
<TextField
|
||||
{...register('ownerSignature')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="ownerSignature"
|
||||
name="ownerSignature"
|
||||
label="Signature on your address"
|
||||
fullWidth
|
||||
error={!!errors.ownerSignature}
|
||||
helperText={errors.ownerSignature?.message}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
@@ -344,7 +362,7 @@ export const BondForm = ({
|
||||
{' '}
|
||||
{`A bonding fee: ${
|
||||
watchNodeType === EnumNodeType.mixnode ? fees.mixnode.amount : fees?.gateway?.amount
|
||||
}`}
|
||||
} ${MAJOR_CURRENCY}`}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { Box } from '@mui/system'
|
||||
import { SuccessReponse, TransactionDetails } from '../../components'
|
||||
import { ClientContext } from '../../context/main'
|
||||
import { ClientContext, MAJOR_CURRENCY } from '../../context/main'
|
||||
|
||||
export const SuccessView: React.FC<{ details?: { amount: string; address: string } }> = ({ details }) => {
|
||||
const { userBalance } = useContext(ClientContext)
|
||||
@@ -17,7 +17,7 @@ export const SuccessView: React.FC<{ details?: { amount: string; address: string
|
||||
<TransactionDetails
|
||||
details={[
|
||||
{ primary: 'Node', secondary: details.address },
|
||||
{ primary: 'Amount', secondary: `${details.amount} punk` },
|
||||
{ primary: 'Amount', secondary: `${details.amount} ${MAJOR_CURRENCY}` },
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as Yup from 'yup'
|
||||
import { MAJOR_CURRENCY } from '../../context/main'
|
||||
import {
|
||||
isValidHostname,
|
||||
validateAmount,
|
||||
@@ -12,16 +13,22 @@ export const validationSchema = Yup.object().shape({
|
||||
identityKey: Yup.string()
|
||||
.required('An indentity key is required')
|
||||
.test('valid-id-key', 'A valid identity key is required', function (value) {
|
||||
return validateKey(value || '')
|
||||
return validateKey(value || '', 32)
|
||||
}),
|
||||
sphinxKey: Yup.string()
|
||||
.required('A sphinx key is required')
|
||||
.test('valid-sphinx-key', 'A valid sphinx key is required', function (value) {
|
||||
return validateKey(value || '')
|
||||
return validateKey(value || '', 32)
|
||||
}),
|
||||
ownerSignature: Yup.string()
|
||||
.required('Signature is required')
|
||||
.test('valid-signature', 'A valid signature is required', function (value) {
|
||||
return validateKey(value || '', 64)
|
||||
}),
|
||||
profitMarginPercent: Yup.number().required('Profit Percentage is required').min(1).max(100),
|
||||
amount: Yup.string()
|
||||
.required('An amount is required')
|
||||
.test('valid-amount', 'A valid amount is required (min 100 punk)', function (value) {
|
||||
.test('valid-amount', `A valid amount is required (min 100 ${MAJOR_CURRENCY})`, function (value) {
|
||||
return validateAmount(value || '', '100000000')
|
||||
// minimum amount needs to come from the backend - replace when available
|
||||
}),
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useForm } from 'react-hook-form'
|
||||
import { EnumNodeType, TFee } from '../../types'
|
||||
import { yupResolver } from '@hookform/resolvers/yup'
|
||||
import { validationSchema } from './validationSchema'
|
||||
import { ClientContext } from '../../context/main'
|
||||
import { ClientContext, MAJOR_CURRENCY } from '../../context/main'
|
||||
import { delegate, majorToMinor } from '../../requests'
|
||||
import { checkHasEnoughFunds } from '../../utils'
|
||||
|
||||
@@ -99,12 +99,14 @@ export const DelegateForm = ({
|
||||
error={!!errors.amount}
|
||||
helperText={errors?.amount?.message}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">punk</InputAdornment>,
|
||||
endAdornment: <InputAdornment position="end">{MAJOR_CURRENCY}</InputAdornment>,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography sx={{ color: 'nym.info' }}>Fee for this transaction: {fees.mixnode.amount} punk</Typography>
|
||||
<Typography sx={{ color: 'nym.info' }}>
|
||||
Fee for this transaction: {`${fees.mixnode.amount} ${MAJOR_CURRENCY}`}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { Box } from '@mui/system'
|
||||
import { SuccessReponse, TransactionDetails } from '../../components'
|
||||
import { ClientContext } from '../../context/main'
|
||||
import { ClientContext, MAJOR_CURRENCY } from '../../context/main'
|
||||
|
||||
export const SuccessView: React.FC<{ details?: { amount: string; address: string } }> = ({ details }) => {
|
||||
const { userBalance } = useContext(ClientContext)
|
||||
@@ -17,7 +17,7 @@ export const SuccessView: React.FC<{ details?: { amount: string; address: string
|
||||
<TransactionDetails
|
||||
details={[
|
||||
{ primary: 'Node', secondary: details.address },
|
||||
{ primary: 'Amount', secondary: `${details.amount} punk` },
|
||||
{ primary: 'Amount', secondary: `${details.amount + MAJOR_CURRENCY}` },
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -7,11 +7,9 @@ export const validationSchema = Yup.object().shape({
|
||||
.test(
|
||||
'valid-id-key',
|
||||
'A valid identity key is required e.g. 824WyExLUWvLE2mpSHBatN4AoByuLzfnHFeHWiBYzg4z',
|
||||
(value) => (!!value ? validateKey(value) : false)
|
||||
(value) => (!!value ? validateKey(value, 32) : false),
|
||||
),
|
||||
amount: Yup.string()
|
||||
.required()
|
||||
.test('valid-amount-key', 'A valid amount is required', (value) =>
|
||||
!!value ? validateAmount(value, '0') : false
|
||||
),
|
||||
.test('valid-amount-key', 'A valid amount is required', (value) => (!!value ? validateAmount(value, '0') : false)),
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { Box, CircularProgress, Link, Typography } from '@mui/material'
|
||||
import { SendError } from './SendError'
|
||||
import { ClientContext, urls } from '../../context/main'
|
||||
import { ClientContext, MAJOR_CURRENCY, urls } from '../../context/main'
|
||||
import { SuccessReponse } from '../../components'
|
||||
import { TransactionDetails } from '../../components/TransactionDetails'
|
||||
import { TransactionDetails as TTransactionDetails } from '../../types'
|
||||
@@ -49,7 +49,7 @@ export const SendConfirmation = ({
|
||||
<TransactionDetails
|
||||
details={[
|
||||
{ primary: 'Recipient', secondary: data.to_address },
|
||||
{ primary: 'Amount', secondary: data.amount.amount + ' punk' },
|
||||
{ primary: 'Amount', secondary: data.amount.amount + MAJOR_CURRENCY },
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { Grid, InputAdornment, TextField, Typography } from '@mui/material'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
import { ClientContext } from '../../context/main'
|
||||
import { ClientContext, MAJOR_CURRENCY } from '../../context/main'
|
||||
|
||||
export const SendForm = ({ transferFee }: { transferFee?: string }) => {
|
||||
const {
|
||||
@@ -43,12 +43,14 @@ export const SendForm = ({ transferFee }: { transferFee?: string }) => {
|
||||
error={!!errors.amount}
|
||||
helperText={errors.amount?.message}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">punk</InputAdornment>,
|
||||
endAdornment: <InputAdornment position="end">{MAJOR_CURRENCY}</InputAdornment>,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography sx={{ color: 'nym.info' }}>Fee for this transaction: {transferFee} punk</Typography>
|
||||
<Typography sx={{ color: 'nym.info' }}>
|
||||
Fee for this transaction: {`${transferFee} ${MAJOR_CURRENCY}`}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react'
|
||||
import { Card, Divider, Grid, Typography } from '@mui/material'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
import {} from '../../context/main'
|
||||
|
||||
export const SendReview = ({ transferFee }: { transferFee?: string }) => {
|
||||
const { getValues } = useFormContext()
|
||||
@@ -32,13 +33,13 @@ export const SendReview = ({ transferFee }: { transferFee?: string }) => {
|
||||
<Divider light />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<SendReviewField title="Amount" subtitle={values.amount + ' punk'} />
|
||||
<SendReviewField title="Amount" subtitle={values.amount + MAJOR_CURRENCY} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider light />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<SendReviewField title="Transfer fee" subtitle={transferFee + ' punk'} info />
|
||||
<SendReviewField title="Transfer fee" subtitle={`${transferFee} ${MAJOR_CURRENCY}`} info />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Card>
|
||||
|
||||
@@ -2,11 +2,12 @@ import React, { useState } from 'react'
|
||||
import { NymCard } from '../../components'
|
||||
import { SendWizard } from './SendWizard'
|
||||
import { Layout } from '../../layouts'
|
||||
import { MAJOR_CURRENCY } from '../../context/main'
|
||||
|
||||
export const Send = () => {
|
||||
return (
|
||||
<Layout>
|
||||
<NymCard title="Send punk" noPadding>
|
||||
<NymCard title={`Send ${MAJOR_CURRENCY}`} noPadding>
|
||||
<SendWizard />
|
||||
</NymCard>
|
||||
</Layout>
|
||||
|
||||
@@ -100,7 +100,9 @@ export const UndelegateForm = ({
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography sx={{ color: 'nym.info' }}>Fee for this transaction: {fees.mixnode.amount} punk</Typography>
|
||||
<Typography sx={{ color: 'nym.info' }}>
|
||||
Fee for this transaction: {`${fees.mixnode.amount} ${MAJOR_CURRENCY}`}{' '}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user