Compare commits

..

25 Commits

Author SHA1 Message Date
Mark Sinclair ae0883241e Network Explorer: change prod env to round robin DNS 2021-12-17 13:07:16 +02:00
Mark Sinclair 11baa99c4b Network Explorer improvements:
- fix up API urls after Network Explorer API changes
- set currency denominations in `.env` file
- set API endpoints in `.env` file
2021-12-17 13:07:16 +02:00
Mark Sinclair 8bc23434ab Network Explorer API improvements:
- upgrade `okapi` for swagger generation across multiple resources
- switched `GET mix-node` to `GET mix-nodes`
- added error message when no geolocation env var is set and process continues
2021-12-17 13:07:16 +02:00
Mark Sinclair 8f6daf1e03 Network Explorer: configure URLs with .env file 2021-12-17 13:07:15 +02:00
Bogdan-Ștefan Neacșu 7f63377d22 Update contract addresses 2021-12-17 13:03:28 +02:00
Bogdan-Ștefan Neacșu 46cb3eca38 Do not set proxy only for this time 2021-12-17 13:02:01 +02:00
Bogdan-Ștefan Neacșu 7b22872c6b Short node identity signature check
Fix tests
2021-12-17 13:02:01 +02:00
Bogdan-Ștefan Neacșu 3342cb13c7 Update network defaults 2021-12-17 13:01:59 +02:00
Bogdan-Ștefan Neacșu 3cd5fc3b22 Make develop branch agnostic of the network 2021-12-17 12:48:29 +02:00
Fouad 0a30eb1c64 Merge pull request #968 from nymtech/feature/new-testnet-wallet-updates
Feature/new testnet wallet updates
2021-12-16 16:08:32 +00:00
Bogdan-Ștefan Neacşu 63bb35e1a1 Use the renamed balance function (#971) 2021-12-16 17:36:50 +02:00
Jędrzej Stuczyński c1e809fd99 Feature/ts client update (#956)
* Made client compile again + set auto fees

* Simplified client construction by allowing only a single URL

* wip

* Simplified signing assertion

* Initial implementation of queries

* Implemented all basic nymd queries

* Validator API queries

* Signing related queries

* Using default arguments

* Removed redundant else branches

* `eslint` and `prettier` formatting on Typescript validator client

* Removed cyclic import on Coin type

* Missing direct dependencies

* Ingoring cyclic imports

* Removed unused argument

Co-authored-by: Mark Sinclair <mmsinclair@gmail.com>
2021-12-16 15:35:30 +00:00
Jędrzej Stuczyński 77ab22999c Feature/node info command (#972)
* Removed code duplication and introduced node-details command for mixnode

* ibid for the gateway

* Added port information
2021-12-16 14:55:03 +00:00
Bogdan-Ștefan Neacşu 747bf85ad8 Add custom denom balance query (#957)
* Add custom denom balance query

* Apply PR comment
2021-12-16 13:59:08 +02:00
Jędrzej Stuczyński cf4eadaa6b Introduced 'version' command to all relevant binaries (#969)
* Introduced 'version' command to all relevant binaries

* Removed separate 'version' subcommand in favour of clap's 'long_version' field
2021-12-16 11:48:50 +00:00
Jędrzej Stuczyński f43f07f0b9 Fixed invalid nodes being counted twice in unroutable category (#963) 2021-12-16 10:58:57 +00:00
fmtabbara c5cf7d19a3 add sample env 2021-12-16 10:10:39 +00:00
fmtabbara 7d7911c8e8 merge develop 2021-12-16 10:09:23 +00:00
Fouad 40d93e1eeb Merge pull request #964 from nymtech/feature/bond-details-handlers
Additional tauri commands to get bond details
2021-12-15 16:53:58 +00:00
Jędrzej Stuczyński 2f472c4e8e Additional tauri commands to get bond details 2021-12-15 16:05:31 +00:00
fmtabbara 140cc3f769 correct profitPercent variable name 2021-12-13 21:11:12 +00:00
fmtabbara 2045d0bafd update new fields 2021-12-13 15:26:35 +00:00
fmtabbara e68b48f296 use config so that env vars can be destructured 2021-12-13 14:39:26 +00:00
fmtabbara 3a2b553e38 use variable for currency 2021-12-13 13:04:17 +00:00
fmtabbara aa1955dc6b update webpack configs 2021-12-13 13:03:23 +00:00
111 changed files with 5729 additions and 8707 deletions
Generated
+88 -6
View File
@@ -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"
+3
View File
@@ -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"] }
+8
View File
@@ -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")
}
+35 -3
View File
@@ -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"),
)
}
+3
View File
@@ -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"] }
+8
View File
@@ -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")
}
+34 -2
View File
@@ -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"),
)
}
+69 -27
View File
@@ -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": "^_" }]
}
}
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"trailingComma": "all",
"singleQuote": true,
"printWidth": 120,
"tabWidth": 2
}
-3848
View File
File diff suppressed because it is too large Load Diff
+21 -9
View File
@@ -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"
}
}
}
-41
View File
@@ -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
View File
@@ -1 +0,0 @@
15.0.1
-59
View File
@@ -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")
}
}
-60
View File
@@ -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")
}
}
+33 -38
View File
@@ -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;
}
File diff suppressed because it is too large Load Diff
-207
View File
@@ -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)
}
}
+182
View File
@@ -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,
},
});
}
}
+210 -139
View File
@@ -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();
}
}
+467
View File
@@ -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,
);
}
}
+8 -20
View File
@@ -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
View File
@@ -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[];
};
+16 -12
View File
@@ -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
})
})
});
-156
View File
@@ -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
View File
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
+11
View File
@@ -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"),
}
}
+19 -14
View File
@@ -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;
+17
View File
@@ -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";
+17
View File
@@ -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(
+2 -10
View File
@@ -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(
+2 -10
View File
@@ -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(
+4
View File
@@ -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();
-10
View File
@@ -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"]
-10
View File
@@ -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"]
+2 -2
View File
@@ -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));
+5 -2
View File
@@ -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
+38 -27
View File
@@ -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())
+2 -6
View File
@@ -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()
}
}
+5
View File
@@ -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(
+6 -12
View File
@@ -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>> {
+20
View File
@@ -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
View File
@@ -1,3 +1,4 @@
pub(crate) mod http;
pub(crate) mod tasks;
mod utils;
+5 -2
View File
@@ -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")]
-3
View File
@@ -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
View File
@@ -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
+10 -9
View File
@@ -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 =
+3 -2
View File
@@ -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();
+16 -17
View File
@@ -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)}%
+2 -2
View File
@@ -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;
}
+6 -12
View File
@@ -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',
+10 -16
View File
@@ -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 -6
View File
@@ -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;
+19
View File
@@ -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,
});
+2 -2
View File
@@ -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 || '',
}));
}
+1
View File
@@ -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"] }
+3
View File
@@ -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")
}
+3 -54
View File
@@ -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()
}
+1
View File
@@ -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;
+35
View File
@@ -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()
}
+3 -51
View File
@@ -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
View File
@@ -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
View File
@@ -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!(
+3
View File
@@ -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"] }
+8
View File
@@ -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")
}
+2 -42
View File
@@ -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()
})
}
+1
View File
@@ -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;
+34
View File
@@ -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()
}
+5 -51
View File
@@ -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()
}
+4
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+4
View File
@@ -0,0 +1,4 @@
MAJOR_CURRENCY=punk
MINOR_CURRENCY=upunk
ADMIN_ADDRESS=
NETWORK_NAME=testnet-milhon
+147 -144
View File
@@ -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]]
+6
View File
@@ -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,
}
+5 -3
View File
@@ -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"
}
}
+2
View File
@@ -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)
}
+4 -2
View File
@@ -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 = {
+20 -36
View File
@@ -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> =>
+42 -24
View File
@@ -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>
)}
+2 -2
View File
@@ -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>
+10 -3
View File
@@ -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 },
]}
/>
</>
+5 -3
View File
@@ -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>
)
+3 -2
View File
@@ -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 -1
View File
@@ -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