Compare commits

..

30 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
Bogdan-Ștefan Neacşu c93f3cfc4f Fix topology log (#962) 2021-12-15 13:19:34 +02:00
Mark Sinclair 87d18bbc16 Merge pull request #960 from nymtech/feature/network-explorer-ui-config
Network Explorer: configure URLs with `.env` file
2021-12-15 10:37:47 +00:00
Mark Sinclair 45ed46afa0 Network Explorer: add prod config 2021-12-15 10:14:41 +00:00
Mark Sinclair 9619e794b2 Network Explorer: configure URLs with .env file 2021-12-14 16:06:02 +00:00
Drazen Urch 305864aa0f Feature/vesting to wallet (#954)
* Better code structure, proper error handling, VestingSigningClient for wallet

* Add vesting contract queries to wallet

* Address review comments

Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>
2021-12-14 12:25:48 +01: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
107 changed files with 6115 additions and 8862 deletions
Generated
+79
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",
]
@@ -7163,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"
-4
View File
@@ -29,7 +29,3 @@ fmt-contracts:
fmt-wallet:
cargo fmt --manifest-path nym-wallet/Cargo.toml --all
+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
@@ -636,7 +636,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
}
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Client {
rpc_client: HttpClient,
signer: DirectSecp256k1HdWallet,
@@ -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>
@@ -52,8 +52,8 @@ pub trait VestingQueryClient {
async fn delegated_vesting(
&self,
block_time: Option<Timestamp>,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
}
@@ -162,8 +162,8 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
async fn delegated_vesting(
&self,
block_time: Option<Timestamp>,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetDelegatedVesting {
vesting_account_address: vesting_account_address.to_string(),
@@ -8,7 +8,7 @@ use crate::nymd::fee::helpers::Operation;
use crate::nymd::{cosmwasm_coin_to_cosmos_coin, NymdClient};
use async_trait::async_trait;
use cosmwasm_std::Coin;
use mixnet_contract::{Gateway, IdentityKey, MixNode};
use mixnet_contract::{Gateway, IdentityKey, IdentityKeyRef, MixNode};
use vesting_contract::messages::ExecuteMsg as VestingExecuteMsg;
#[async_trait]
@@ -16,8 +16,8 @@ pub trait VestingSigningClient {
async fn vesting_bond_gateway(
&self,
gateway: Gateway,
pledge: Coin,
owner_signature: &str,
pledge: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_unbond_gateway(&self) -> Result<ExecuteResult, NymdError>;
@@ -31,8 +31,8 @@ pub trait VestingSigningClient {
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
pledge: Coin,
owner_signature: &str,
pledge: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_unbond_mixnode(&self) -> Result<ExecuteResult, NymdError>;
@@ -51,15 +51,15 @@ pub trait VestingSigningClient {
amount: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_delegate_to_mixnode(
async fn vesting_delegate_to_mixnode<'a>(
&self,
mix_identity: IdentityKey,
amount: Coin,
mix_identity: IdentityKeyRef<'a>,
amount: &Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_undelegate_from_mixnode(
async fn vesting_undelegate_from_mixnode<'a>(
&self,
mix_identity: IdentityKey,
mix_identity: IdentityKeyRef<'a>,
) -> Result<ExecuteResult, NymdError>;
async fn create_periodic_vesting_account(
@@ -75,8 +75,8 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
async fn vesting_bond_gateway(
&self,
gateway: Gateway,
pledge: Coin,
owner_signature: &str,
pledge: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::BondGateway);
let req = VestingExecuteMsg::BondGateway {
@@ -135,8 +135,8 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
pledge: Coin,
owner_signature: &str,
pledge: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::BondMixnode);
let req = VestingExecuteMsg::BondMixnode {
@@ -230,13 +230,15 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
)
.await
}
async fn vesting_delegate_to_mixnode(
async fn vesting_delegate_to_mixnode<'a>(
&self,
mix_identity: IdentityKey,
amount: Coin,
mix_identity: IdentityKeyRef<'a>,
amount: &Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::DelegateToMixnode);
let req = VestingExecuteMsg::DelegateToMixnode { mix_identity };
let req = VestingExecuteMsg::DelegateToMixnode {
mix_identity: mix_identity.into(),
};
self.client
.execute(
self.address(),
@@ -244,16 +246,18 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
&req,
fee,
"VestingContract::DeledateToMixnode",
vec![cosmwasm_coin_to_cosmos_coin(amount)],
vec![cosmwasm_coin_to_cosmos_coin(amount.to_owned())],
)
.await
}
async fn vesting_undelegate_from_mixnode(
async fn vesting_undelegate_from_mixnode<'a>(
&self,
mix_identity: IdentityKey,
mix_identity: IdentityKeyRef<'a>,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::UndelegateFromMixnode);
let req = VestingExecuteMsg::UndelegateFromMixnode { mix_identity };
let req = VestingExecuteMsg::UndelegateFromMixnode {
mix_identity: mix_identity.into(),
};
self.client
.execute(
self.address(),
@@ -10,7 +10,7 @@ use cosmrs::tx::SignDoc;
use cosmrs::{tx, AccountId};
/// Derivation information required to derive a keypair and an address from a mnemonic.
#[derive(Debug)]
#[derive(Debug, Clone)]
struct Secp256k1Derivation {
hd_path: DerivationPath,
prefix: String,
@@ -40,7 +40,7 @@ impl AccountData {
type Secp256k1Keypair = (SigningKey, PublicKey);
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct DirectSecp256k1HdWallet {
/// Base secret
secret: bip39::Mnemonic,
@@ -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"),
}
}
+22 -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,18 @@ impl ValidatorDetails {
}
}
#[cfg(network = "milhon")]
pub fn default_validators() -> Vec<ValidatorDetails> {
vec![
ValidatorDetails::new(
"https://testnet-milhon-validator1.nymtech.net",
Some("https://testnet-milhon-validator1.nymtech.net/api"),
),
ValidatorDetails::new("https://testnet-milhon-validator2.nymtech.net", None),
]
}
#[cfg(network = "sandbox")]
pub fn default_validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://sandbox-validator.nymtech.net",
@@ -59,10 +80,7 @@ pub fn default_api_endpoints() -> Vec<Url> {
.collect()
}
pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = "nymt14hj2tavq8fpesdwxxcu44rty3hh90vhuysqrsr";
pub const DEFAULT_VESTING_CONTRACT_ADDRESS: &str = "nymt1nc5tatafv6eyq7llkr2gv50ff9e22mnfp9pc5s";
pub const REWARDING_VALIDATOR_ADDRESS: &str = "nymt17zujduc46wvkwvp6f062mm5xhr7jc3fewvqu9e";
// 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
@@ -70,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 = "nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv";
// 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 = "nymt";
pub const DENOM: &str = "unymt";
// 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";
+11 -8
View File
@@ -13,7 +13,7 @@ interface ChartProps {
loading: boolean;
}
type FormattedDateRecord = [string, number];
type FormattedDateRecord = [string, number, number];
type FormattedChartHeadings = string[];
type FormattedChartData = [FormattedChartHeadings | FormattedDateRecord];
@@ -30,19 +30,22 @@ export const UptimeChart: React.FC<ChartProps> = ({
const color = theme.palette.text.primary;
React.useEffect(() => {
if (uptimeStory.data?.history) {
const allFormattedChartData: FormattedChartData = [['Date', 'Uptime']];
const allFormattedChartData: FormattedChartData = [
['Date', 'UptimeV4', 'UptimeV6'],
];
uptimeStory.data.history.forEach((eachDate) => {
const formattedDateUptimeRecord: FormattedDateRecord = [
format(new Date(eachDate.date), 'MMM dd'),
eachDate.uptime,
eachDate.ipv4_uptime,
eachDate.ipv6_uptime,
];
allFormattedChartData.push(formattedDateUptimeRecord);
});
setFormattedChartData(allFormattedChartData);
} else {
const emptyData: any = [
['Date', 'Uptime'],
['Jul 27', 10],
['Date', 'UptimeV4', 'UptimeV6'],
['Jul 27', 10, 10],
];
setFormattedChartData(emptyData);
}
@@ -62,8 +65,8 @@ export const UptimeChart: React.FC<ChartProps> = ({
uptimeStory.data
? formattedChartData
: [
['Date', 'Uptime'],
[format(new Date(Date.now()), 'MMM dd'), 0],
['Date', 'UptimeV4', 'UptimeV6'],
[format(new Date(Date.now()), 'MMM dd'), 0, 0],
]
}
options={{
@@ -74,7 +77,7 @@ export const UptimeChart: React.FC<ChartProps> = ({
color: uptimeStory.error
? 'rgba(255, 255, 255, 0.4)'
: 'rgba(255, 255, 255, 1)',
colors: ['#FB7A21'],
colors: ['#FB7A21', '#CC808A'],
legend: {
textStyle: {
color,
+2 -1
View File
@@ -157,7 +157,8 @@ export type StatusResponse = {
export type UptimeTime = {
date: string;
uptime: number;
ipv4_uptime: number;
ipv6_uptime: number;
};
export type UptimeStoryResponse = {
+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
+148 -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",
]
@@ -2709,6 +2708,7 @@ dependencies = [
"cosmwasm-std",
"credentials",
"dirs",
"eyre",
"mixnet-contract",
"rand 0.6.5",
"serde",
@@ -2779,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"
@@ -2797,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",
@@ -2827,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",
@@ -3040,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",
@@ -3169,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"
@@ -3277,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",
]
@@ -3541,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]]
@@ -3638,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",
@@ -3687,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",
@@ -3729,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"
@@ -3756,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"
@@ -3787,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",
@@ -3799,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",
@@ -3900,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",
]
@@ -3927,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",
@@ -3949,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",
]
@@ -3976,7 +3995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97"
dependencies = [
"dtoa",
"itoa",
"itoa 0.4.8",
"serde",
"url",
]
@@ -3988,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",
]
@@ -4510,7 +4529,7 @@ dependencies = [
"ndk-sys",
"objc",
"parking_lot",
"raw-window-handle",
"raw-window-handle 0.3.4",
"scopeguard",
"serde",
"unicode-segmentation",
@@ -4520,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",
@@ -4552,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",
@@ -4660,7 +4679,7 @@ checksum = "fcb9b79594f22b6ed0cc8362e0dfde5b7969962de3cd8ca683de702e59e8221b"
dependencies = [
"html5ever",
"kuchiki",
"phf 0.10.0",
"phf 0.10.1",
"proc-macro2",
"quote",
"serde",
@@ -4686,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",
@@ -4712,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",
@@ -4731,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",
@@ -4745,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",
@@ -4773,6 +4791,7 @@ dependencies = [
"tendermint-config",
"tendermint-proto",
"thiserror",
"time 0.3.5",
"tokio",
"tracing",
"url",
@@ -4838,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",
@@ -4854,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",
@@ -4871,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",
@@ -4973,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"
}
}
+1
View File
@@ -26,6 +26,7 @@ thiserror = "1.0"
tendermint-rpc = "0.23.0"
url = "2.0"
rand = "0.6.5"
eyre = "0.6.5"
cosmrs = { git = "https://github.com/cosmos/cosmos-rust", rev="e5a1872083abb3d88fa62dda966e7f5408deba58", features = ["rpc", "bip32", "cosmwasm"] }
+10 -14
View File
@@ -1,5 +1,6 @@
// This should be moved out of the wallet, and used as a primary coin type throughout the codebase
use crate::error::BackendError;
use ::config::defaults::DENOM;
use cosmrs::Decimal;
use cosmrs::Denom as CosmosDenom;
@@ -12,8 +13,6 @@ use std::ops::{Add, Sub};
use std::str::FromStr;
use validator_client::nymd::{CosmosCoin, GasPrice};
use crate::format_err;
#[cfg_attr(test, derive(ts_rs::TS))]
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub enum Denom {
@@ -33,19 +32,16 @@ impl fmt::Display for Denom {
}
impl FromStr for Denom {
type Err = String;
type Err = BackendError;
fn from_str(s: &str) -> Result<Denom, String> {
fn from_str(s: &str) -> Result<Denom, BackendError> {
let s = s.to_lowercase();
if s == DENOM.to_lowercase() || s == "minor" {
Ok(Denom::Minor)
} else if s == DENOM[1..].to_lowercase() || s == "major" {
Ok(Denom::Major)
} else {
Err(format_err!(format!(
"{} is not a valid denomination string",
s
)))
Err(BackendError::InvalidDenom(s))
}
}
}
@@ -169,9 +165,9 @@ impl Coin {
}
impl TryFrom<Coin> for CosmWasmCoin {
type Error = String;
type Error = BackendError;
fn try_from(coin: Coin) -> Result<CosmWasmCoin, String> {
fn try_from(coin: Coin) -> Result<CosmWasmCoin, Self::Error> {
Ok(CosmWasmCoin::new(
Uint128::try_from(coin.amount.as_str()).unwrap().u128(),
coin.denom.to_string(),
@@ -180,15 +176,15 @@ impl TryFrom<Coin> for CosmWasmCoin {
}
impl TryFrom<Coin> for CosmosCoin {
type Error = String;
type Error = BackendError;
fn try_from(coin: Coin) -> Result<CosmosCoin, String> {
fn try_from(coin: Coin) -> Result<CosmosCoin, BackendError> {
match Decimal::from_str(&coin.amount) {
Ok(d) => Ok(CosmosCoin {
amount: d,
denom: CosmosDenom::from_str(&coin.denom.to_string()).unwrap(),
denom: CosmosDenom::from_str(&coin.denom.to_string())?,
}),
Err(e) => Err(format_err!(e)),
Err(e) => Err(e.into()),
}
}
}
+29 -3
View File
@@ -1,21 +1,47 @@
use serde::{Serialize, Serializer};
use thiserror::Error;
use validator_client::nymd::error::NymdError;
#[derive(Error, Debug)]
pub enum BackendError {
#[error("Error parsing bip39 mnemonic")]
#[error("{source}")]
Bip39Error {
#[from]
source: bip39::Error,
},
#[error("Error parsing into tendermint Url")]
#[error("{source}")]
TendermintError {
#[from]
source: tendermint_rpc::Error,
},
#[error("Error getting balances")]
#[error("{source}")]
NymdError {
#[from]
source: NymdError,
},
#[error("{source}")]
CosmwasmStd {
#[from]
source: cosmwasm_std::StdError,
},
#[error("{source}")]
ErrorReport {
#[from]
source: eyre::Report,
},
#[error("Client has not been initialized yet, connect with mnemonic to initialize")]
ClientNotInitialized,
#[error("No balance available for address {0}")]
NoBalance(String),
#[error("{0} is not a valid denomination string")]
InvalidDenom(String),
}
impl Serialize for BackendError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
+45 -42
View File
@@ -18,47 +18,50 @@ mod state;
mod utils;
use crate::menu::AddDefaultSubmenus;
use crate::operations::account::*;
use crate::operations::admin::*;
use crate::operations::bond::*;
use crate::operations::delegate::*;
use crate::operations::send::*;
use crate::utils::*;
use crate::operations::mixnet;
use crate::operations::vesting;
use crate::state::State;
#[cfg(test)]
use crate::coin::{Coin, Denom};
#[macro_export]
macro_rules! format_err {
($e:expr) => {
format!("line {}: {}", line!(), $e)
};
}
fn main() {
tauri::Builder::default()
.manage(Arc::new(RwLock::new(State::default())))
.invoke_handler(tauri::generate_handler![
connect_with_mnemonic,
get_balance,
minor_to_major,
major_to_minor,
owns_gateway,
owns_mixnode,
bond_mixnode,
unbond_mixnode,
bond_gateway,
unbond_gateway,
delegate_to_mixnode,
undelegate_from_mixnode,
send,
create_new_account,
get_approximate_fee,
get_contract_settings,
update_contract_settings,
get_reverse_mix_delegations_paged,
mixnet::account::connect_with_mnemonic,
mixnet::account::create_new_account,
mixnet::account::get_balance,
mixnet::admin::get_contract_settings,
mixnet::admin::update_contract_settings,
mixnet::bond::bond_gateway,
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,
mixnet::send::send,
utils::get_approximate_fee,
utils::major_to_minor,
utils::minor_to_major,
utils::owns_gateway,
utils::owns_mixnode,
vesting::bond::vesting_bond_gateway,
vesting::bond::vesting_bond_mixnode,
vesting::bond::vesting_unbond_gateway,
vesting::bond::vesting_unbond_mixnode,
vesting::delegate::vesting_delegate_to_mixnode,
vesting::delegate::vesting_undelegate_from_mixnode,
vesting::queries::locked_coins,
vesting::queries::spendable_coins,
vesting::queries::vesting_coins,
vesting::queries::vested_coins,
vesting::queries::vesting_start_time,
vesting::queries::vesting_end_time,
vesting::queries::original_vesting,
vesting::queries::delegated_free,
vesting::queries::delegated_vesting,
])
.menu(Menu::new().add_default_app_submenu_if_macos())
.run(tauri::generate_context!())
@@ -69,15 +72,15 @@ fn main() {
mod test {
ts_rs::export! {
mixnet_contract::MixNode => "../src/types/rust/mixnode.ts",
crate::Coin => "../src/types/rust/coin.ts",
crate::Balance => "../src/types/rust/balance.ts",
crate::coin::Coin => "../src/types/rust/coin.ts",
crate::mixnet::account::Balance => "../src/types/rust/balance.ts",
mixnet_contract::Gateway => "../src/types/rust/gateway.ts",
crate::TauriTxResult => "../src/types/rust/tauritxresult.ts",
crate::TransactionDetails => "../src/types/rust/transactiondetails.ts",
crate::mixnet::send::TauriTxResult => "../src/types/rust/tauritxresult.ts",
crate::mixnet::send::TransactionDetails => "../src/types/rust/transactiondetails.ts",
validator_client::nymd::fee::helpers::Operation => "../src/types/rust/operation.ts",
crate::Denom => "../src/types/rust/denom.ts",
crate::DelegationResult => "../src/types/rust/delegationresult.ts",
crate::Account => "../src/types/rust/account.ts",
crate::TauriContractStateParams => "../src/types/rust/stateparams.ts"
crate::coin::Denom => "../src/types/rust/denom.ts",
crate::utils::DelegationResult => "../src/types/rust/delegationresult.ts",
crate::mixnet::account::Account => "../src/types/rust/account.ts",
crate::mixnet::admin::TauriContractStateParams => "../src/types/rust/stateparams.ts"
}
}
@@ -1,66 +0,0 @@
use crate::coin::Coin;
use crate::format_err;
use crate::state::State;
use crate::{Gateway, MixNode};
use cosmwasm_std::Coin as CosmWasmCoin;
use std::convert::TryInto;
use std::sync::Arc;
use tokio::sync::RwLock;
#[tauri::command]
pub async fn bond_gateway(
gateway: Gateway,
pledge: Coin,
owner_signature: String,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), String> {
let r_state = state.read().await;
let pledge: CosmWasmCoin = match pledge.try_into() {
Ok(b) => b,
Err(e) => return Err(format_err!(e)),
};
let client = r_state.client()?;
match client.bond_gateway(gateway, owner_signature, pledge).await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
pub async fn unbond_gateway(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<(), String> {
let r_state = state.read().await;
let client = r_state.client()?;
match client.unbond_gateway().await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
pub async fn unbond_mixnode(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<(), String> {
let r_state = state.read().await;
let client = r_state.client()?;
match client.unbond_mixnode().await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
pub async fn bond_mixnode(
mixnode: MixNode,
owner_signature: String,
pledge: Coin,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), String> {
let r_state = state.read().await;
let pledge: CosmWasmCoin = match pledge.try_into() {
Ok(b) => b,
Err(e) => return Err(format_err!(e)),
};
let client = r_state.client()?;
match client.bond_mixnode(mixnode, owner_signature, pledge).await {
Ok(_result) => Ok(()),
Err(e) => Err(format_err!(e)),
}
}
@@ -1,68 +0,0 @@
use crate::coin::Coin;
use crate::format_err;
use crate::state::State;
use cosmwasm_std::Coin as CosmWasmCoin;
use mixnet_contract::PagedDelegatorDelegationsResponse;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::sync::Arc;
use tokio::sync::RwLock;
#[cfg_attr(test, derive(ts_rs::TS))]
#[derive(Serialize, Deserialize)]
pub struct DelegationResult {
source_address: String,
target_address: String,
amount: Option<Coin>,
}
#[tauri::command]
pub async fn delegate_to_mixnode(
identity: &str,
amount: Coin,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<DelegationResult, String> {
let r_state = state.read().await;
let delegation: CosmWasmCoin = match amount.try_into() {
Ok(b) => b,
Err(e) => return Err(format_err!(e)),
};
let client = r_state.client()?;
match client.delegate_to_mixnode(identity, &delegation).await {
Ok(_result) => Ok(DelegationResult {
source_address: client.address().to_string(),
target_address: identity.to_string(),
amount: Some(delegation.into()),
}),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
pub async fn undelegate_from_mixnode(
identity: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<DelegationResult, String> {
let r_state = state.read().await;
let client = r_state.client()?;
match client.remove_mixnode_delegation(identity).await {
Ok(_result) => Ok(DelegationResult {
source_address: client.address().to_string(),
target_address: identity.to_string(),
amount: None,
}),
Err(e) => Err(format_err!(e)),
}
}
#[tauri::command]
pub async fn get_reverse_mix_delegations_paged(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<PagedDelegatorDelegationsResponse, String> {
let r_state = state.read().await;
let client = r_state.client()?;
client
.get_delegator_delegations_paged(client.address().to_string(), None, None)
.await
.map_err(|err| format_err!(err))
}
@@ -1,7 +1,7 @@
use crate::client;
use crate::coin::{Coin, Denom};
use crate::config::Config;
use crate::error::BackendError;
use crate::format_err;
use crate::state::State;
use bip39::{Language, Mnemonic};
use serde::{Deserialize, Serialize};
@@ -30,26 +30,16 @@ pub struct Balance {
pub async fn connect_with_mnemonic(
mnemonic: String,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Account, String> {
let mnemonic = match Mnemonic::from_str(&mnemonic) {
Ok(mnemonic) => mnemonic,
Err(e) => return Err(BackendError::from(e).to_string()),
};
let client;
{
) -> Result<Account, BackendError> {
let mnemonic = Mnemonic::from_str(&mnemonic)?;
let client = {
let r_state = state.read().await;
client = _connect_with_mnemonic(mnemonic, &r_state.config());
}
_connect_with_mnemonic(mnemonic, &r_state.config())
};
let contract_address = match client.mixnet_contract_address() {
Ok(address) => address.to_string(),
Err(e) => return Err(format_err!(e)),
};
let contract_address = client.mixnet_contract_address()?.to_string();
let client_address = client.address().to_string();
let denom = match client.denom() {
Ok(denom) => denom,
Err(e) => return Err(format_err!(e)),
};
let denom = client.denom()?;
let account = Account {
contract_address,
@@ -65,10 +55,13 @@ pub async fn connect_with_mnemonic(
}
#[tauri::command]
pub async fn get_balance(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<Balance, String> {
let r_state = state.read().await;
let client = r_state.client()?;
match client.get_balance(client.address()).await {
pub async fn get_balance(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Balance, BackendError> {
match client!(state)
.get_mixnet_balance(client!(state).address())
.await
{
Ok(Some(coin)) => {
let coin = Coin::new(
&coin.amount.to_string(),
@@ -79,18 +72,17 @@ pub async fn get_balance(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<
printable_balance: coin.to_major().to_string(),
})
}
Ok(None) => Err(format!(
"No balance available for address {}",
client.address()
Ok(None) => Err(BackendError::NoBalance(
client!(state).address().to_string(),
)),
Err(e) => Err(BackendError::from(e).to_string()),
Err(e) => Err(BackendError::from(e)),
}
}
#[tauri::command]
pub async fn create_new_account(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Account, String> {
) -> Result<Account, BackendError> {
let rand_mnemonic = random_mnemonic();
let mut client = connect_with_mnemonic(rand_mnemonic.to_string(), state).await?;
client.mnemonic = Some(rand_mnemonic.to_string());
@@ -1,4 +1,5 @@
use crate::format_err;
use crate::client;
use crate::error::BackendError;
use crate::state::State;
use cosmwasm_std::Uint128;
use mixnet_contract::ContractStateParams;
@@ -30,7 +31,7 @@ impl From<ContractStateParams> for TauriContractStateParams {
}
impl TryFrom<TauriContractStateParams> for ContractStateParams {
type Error = Box<dyn std::error::Error>;
type Error = BackendError;
fn try_from(p: TauriContractStateParams) -> Result<ContractStateParams, Self::Error> {
Ok(ContractStateParams {
@@ -46,31 +47,18 @@ impl TryFrom<TauriContractStateParams> for ContractStateParams {
#[tauri::command]
pub async fn get_contract_settings(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TauriContractStateParams, String> {
let r_state = state.read().await;
let client = r_state.client()?;
match client.get_contract_settings().await {
Ok(params) => Ok(params.into()),
Err(e) => Err(format_err!(e)),
}
) -> Result<TauriContractStateParams, BackendError> {
Ok(client!(state).get_contract_settings().await?.into())
}
#[tauri::command]
pub async fn update_contract_settings(
params: TauriContractStateParams,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TauriContractStateParams, String> {
let r_state = state.read().await;
let client = r_state.client()?;
let mixnet_contract_settings_params: ContractStateParams = match params.try_into() {
Ok(mixnet_contract_settings_params) => mixnet_contract_settings_params,
Err(e) => return Err(format_err!(e)),
};
match client
) -> Result<TauriContractStateParams, BackendError> {
let mixnet_contract_settings_params: ContractStateParams = params.try_into()?;
client!(state)
.update_contract_settings(mixnet_contract_settings_params.clone())
.await
{
Ok(_) => Ok(mixnet_contract_settings_params.into()),
Err(e) => Err(format_err!(e)),
}
.await?;
Ok(mixnet_contract_settings_params.into())
}
@@ -0,0 +1,71 @@
use crate::client;
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;
#[tauri::command]
pub async fn bond_gateway(
gateway: Gateway,
pledge: Coin,
owner_signature: String,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
client!(state)
.bond_gateway(gateway, owner_signature, pledge.try_into()?)
.await?;
Ok(())
}
#[tauri::command]
pub async fn unbond_gateway(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
client!(state).unbond_gateway().await?;
Ok(())
}
#[tauri::command]
pub async fn unbond_mixnode(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
client!(state).unbond_mixnode().await?;
Ok(())
}
#[tauri::command]
pub async fn bond_mixnode(
mixnode: MixNode,
owner_signature: String,
pledge: Coin,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
client!(state)
.bond_mixnode(mixnode, owner_signature, pledge.try_into()?)
.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)
}
@@ -0,0 +1,51 @@
use crate::client;
use crate::coin::Coin;
use crate::error::BackendError;
use crate::state::State;
use crate::utils::DelegationResult;
use cosmwasm_std::Coin as CosmWasmCoin;
use mixnet_contract::PagedDelegatorDelegationsResponse;
use std::convert::TryInto;
use std::sync::Arc;
use tokio::sync::RwLock;
#[tauri::command]
pub async fn delegate_to_mixnode(
identity: &str,
amount: Coin,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<DelegationResult, BackendError> {
let delegation: CosmWasmCoin = amount.try_into()?;
client!(state)
.delegate_to_mixnode(identity, &delegation)
.await?;
Ok(DelegationResult::new(
&client!(state).address().to_string(),
identity,
Some(delegation.into()),
))
}
#[tauri::command]
pub async fn undelegate_from_mixnode(
identity: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<DelegationResult, BackendError> {
client!(state).remove_mixnode_delegation(identity).await?;
Ok(DelegationResult::new(
&client!(state).address().to_string(),
identity,
None,
))
}
#[tauri::command]
pub async fn get_reverse_mix_delegations_paged(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<PagedDelegatorDelegationsResponse, BackendError> {
Ok(
client!(state)
.get_delegator_delegations_paged(client!(state).address().to_string(), None, None)
.await?,
)
}
@@ -0,0 +1,5 @@
pub mod account;
pub mod admin;
pub mod bond;
pub mod delegate;
pub mod send;
@@ -1,5 +1,6 @@
use crate::client;
use crate::coin::Coin;
use crate::format_err;
use crate::error::BackendError;
use crate::state::State;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
@@ -47,26 +48,18 @@ pub async fn send(
amount: Coin,
memo: String,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<TauriTxResult, String> {
let address = match AccountId::from_str(address) {
Ok(addy) => addy,
Err(e) => return Err(format_err!(e)),
};
let cosmos_amount: CosmosCoin = match amount.clone().try_into() {
Ok(b) => b,
Err(e) => return Err(format_err!(e)),
};
let r_state = state.read().await;
let client = r_state.client()?;
match client.send(&address, vec![cosmos_amount], memo).await {
Ok(result) => Ok(TauriTxResult::new(
result,
TransactionDetails {
from_address: client.address().to_string(),
to_address: address.to_string(),
amount,
},
)),
Err(e) => Err(format_err!(e)),
}
) -> Result<TauriTxResult, BackendError> {
let address = AccountId::from_str(address)?;
let cosmos_amount: CosmosCoin = amount.clone().try_into()?;
let result = client!(state)
.send(&address, vec![cosmos_amount], memo)
.await?;
Ok(TauriTxResult::new(
result,
TransactionDetails {
from_address: client!(state).address().to_string(),
to_address: address.to_string(),
amount,
},
))
}
+2 -5
View File
@@ -1,5 +1,2 @@
pub mod account;
pub mod admin;
pub mod bond;
pub mod delegate;
pub mod send;
pub mod mixnet;
pub mod vesting;
@@ -0,0 +1,51 @@
use crate::client;
use crate::coin::Coin;
use crate::error::BackendError;
use crate::state::State;
use crate::{Gateway, MixNode};
use std::convert::TryInto;
use std::sync::Arc;
use tokio::sync::RwLock;
use validator_client::nymd::VestingSigningClient;
#[tauri::command]
pub async fn vesting_bond_gateway(
gateway: Gateway,
pledge: Coin,
owner_signature: String,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
client!(state)
.vesting_bond_gateway(gateway, &owner_signature, pledge.try_into()?)
.await?;
Ok(())
}
#[tauri::command]
pub async fn vesting_unbond_gateway(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
client!(state).vesting_unbond_gateway().await?;
Ok(())
}
#[tauri::command]
pub async fn vesting_unbond_mixnode(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
client!(state).vesting_unbond_mixnode().await?;
Ok(())
}
#[tauri::command]
pub async fn vesting_bond_mixnode(
mixnode: MixNode,
owner_signature: String,
pledge: Coin,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
client!(state)
.vesting_bond_mixnode(mixnode, &owner_signature, pledge.try_into()?)
.await?;
Ok(())
}
@@ -0,0 +1,42 @@
use crate::client;
use crate::coin::Coin;
use crate::error::BackendError;
use crate::state::State;
use crate::utils::DelegationResult;
use cosmwasm_std::Coin as CosmWasmCoin;
use std::convert::TryInto;
use std::sync::Arc;
use tokio::sync::RwLock;
use validator_client::nymd::VestingSigningClient;
#[tauri::command]
pub async fn vesting_delegate_to_mixnode(
identity: &str,
amount: Coin,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<DelegationResult, BackendError> {
let delegation: CosmWasmCoin = amount.try_into()?;
client!(state)
.vesting_delegate_to_mixnode(identity, &delegation)
.await?;
Ok(DelegationResult::new(
&client!(state).address().to_string(),
identity,
Some(delegation.into()),
))
}
#[tauri::command]
pub async fn vesting_undelegate_from_mixnode(
identity: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<DelegationResult, BackendError> {
client!(state)
.vesting_undelegate_from_mixnode(identity)
.await?;
Ok(DelegationResult::new(
&client!(state).address().to_string(),
identity,
None,
))
}
@@ -0,0 +1,3 @@
pub mod bond;
pub mod delegate;
pub mod queries;
@@ -0,0 +1,146 @@
use crate::client;
use crate::coin::Coin;
use crate::error::BackendError;
use crate::state::State;
use cosmwasm_std::Timestamp;
use std::sync::Arc;
use tokio::sync::RwLock;
use validator_client::nymd::VestingQueryClient;
#[tauri::command]
pub async fn locked_coins(
address: &str,
block_time: Option<u64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Coin, BackendError> {
Ok(
client!(state)
.locked_coins(address, block_time.map(Timestamp::from_seconds))
.await?
.into(),
)
}
#[tauri::command]
pub async fn spendable_coins(
vesting_account_address: &str,
block_time: Option<u64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Coin, BackendError> {
Ok(
client!(state)
.spendable_coins(
vesting_account_address,
block_time.map(Timestamp::from_seconds),
)
.await?
.into(),
)
}
#[tauri::command]
pub async fn vested_coins(
vesting_account_address: &str,
block_time: Option<u64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Coin, BackendError> {
Ok(
client!(state)
.vested_coins(
vesting_account_address,
block_time.map(Timestamp::from_seconds),
)
.await?
.into(),
)
}
#[tauri::command]
pub async fn vesting_coins(
vesting_account_address: &str,
block_time: Option<u64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Coin, BackendError> {
Ok(
client!(state)
.vesting_coins(
vesting_account_address,
block_time.map(Timestamp::from_seconds),
)
.await?
.into(),
)
}
#[tauri::command]
pub async fn vesting_start_time(
vesting_account_address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<u64, BackendError> {
Ok(
client!(state)
.vesting_start_time(vesting_account_address)
.await?
.seconds(),
)
}
#[tauri::command]
pub async fn vesting_end_time(
vesting_account_address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<u64, BackendError> {
Ok(
client!(state)
.vesting_end_time(vesting_account_address)
.await?
.seconds(),
)
}
#[tauri::command]
pub async fn original_vesting(
vesting_account_address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Coin, BackendError> {
Ok(
client!(state)
.original_vesting(vesting_account_address)
.await?
.into(),
)
}
#[tauri::command]
pub async fn delegated_free(
vesting_account_address: &str,
block_time: Option<u64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Coin, BackendError> {
Ok(
client!(state)
.delegated_free(
vesting_account_address,
block_time.map(Timestamp::from_seconds),
)
.await?
.into(),
)
}
#[tauri::command]
pub async fn delegated_vesting(
block_time: Option<u64>,
vesting_account_address: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Coin, BackendError> {
Ok(
client!(state)
.delegated_vesting(
vesting_account_address,
block_time.map(Timestamp::from_seconds),
)
.await?
.into(),
)
}
+13 -4
View File
@@ -1,4 +1,5 @@
use crate::config::Config;
use crate::error::BackendError;
use validator_client::nymd::{NymdClient, SigningNymdClient};
#[derive(Default)]
@@ -8,10 +9,11 @@ pub struct State {
}
impl State {
pub fn client(&self) -> Result<&NymdClient<SigningNymdClient>, String> {
self.signing_client.as_ref().ok_or_else(|| {
"Client has not been initialized yet, connect with mnemonic to initialize".to_string()
})
pub fn client(&self) -> Result<&NymdClient<SigningNymdClient>, BackendError> {
self
.signing_client
.as_ref()
.ok_or(BackendError::ClientNotInitialized)
}
pub fn config(&self) -> Config {
@@ -22,3 +24,10 @@ impl State {
self.signing_client = Some(signing_client)
}
}
#[macro_export]
macro_rules! client {
($state:ident) => {
$state.read().await.client()?
};
}
+45 -24
View File
@@ -1,40 +1,46 @@
use crate::client;
use crate::coin::{Coin, Denom};
use crate::format_err;
use crate::error::BackendError;
use crate::state::State;
use crate::Operation;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
#[tauri::command]
pub fn major_to_minor(amount: &str) -> Result<Coin, String> {
pub fn major_to_minor(amount: &str) -> Coin {
let coin = Coin::new(amount, &Denom::Major);
Ok(coin.to_minor())
coin.to_minor()
}
#[tauri::command]
pub fn minor_to_major(amount: &str) -> Result<Coin, String> {
pub fn minor_to_major(amount: &str) -> Coin {
let coin = Coin::new(amount, &Denom::Minor);
Ok(coin.to_major())
coin.to_major()
}
#[tauri::command]
pub async fn owns_mixnode(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<bool, String> {
let r_state = state.read().await;
let client = r_state.client()?;
match client.owns_mixnode(client.address()).await {
Ok(o) => Ok(o.is_some()),
Err(e) => Err(format_err!(e)),
}
pub async fn owns_mixnode(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<bool, BackendError> {
Ok(
client!(state)
.owns_mixnode(client!(state).address())
.await?
.is_some(),
)
}
#[tauri::command]
pub async fn owns_gateway(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<bool, String> {
let r_state = state.read().await;
let client = r_state.client()?;
match client.owns_gateway(client.address()).await {
Ok(o) => Ok(o.is_some()),
Err(e) => Err(format_err!(e)),
}
pub async fn owns_gateway(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<bool, BackendError> {
Ok(
client!(state)
.owns_gateway(client!(state).address())
.await?
.is_some(),
)
}
// NOTE: this uses OUTDATED defaults that might have no resemblance with the reality
@@ -43,14 +49,29 @@ pub async fn owns_gateway(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result
pub async fn get_approximate_fee(
operation: Operation,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Coin, String> {
let r_state = state.read().await;
let client = r_state.client()?;
let approximate_fee = operation.default_fee(client.gas_price());
) -> Result<Coin, BackendError> {
let approximate_fee = operation.default_fee(client!(state).gas_price());
let mut coin = Coin::new("0", &Denom::Major);
for f in approximate_fee.amount {
coin = coin + f.into();
}
Ok(coin)
}
#[cfg_attr(test, derive(ts_rs::TS))]
#[derive(Serialize, Deserialize)]
pub struct DelegationResult {
source_address: String,
target_address: String,
amount: Option<Coin>,
}
impl DelegationResult {
pub fn new(source_address: &str, target_address: &str, amount: Option<Coin>) -> DelegationResult {
DelegationResult {
source_address: source_address.to_string(),
target_address: target_address.to_string(),
amount,
}
}
}
+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>
@@ -5,6 +5,6 @@ export const validationSchema = Yup.object().shape({
identity: Yup.string()
.required()
.test('valid-id-key', 'A valid identity key is required', function (value) {
return validateKey(value || '')
return validateKey(value || '', 32)
}),
})
+6 -11
View File
@@ -4,22 +4,19 @@ import { minor, valid } from 'semver'
import { userBalance, majorToMinor } from '../requests'
import { Coin } from '../types'
export const validateKey = (key: string): boolean => {
export const validateKey = (key: string, bytesLength: number): boolean => {
// it must be a valid base58 key
try {
const bytes = bs58.decode(key)
// of length 32
return bytes.length === 32
return bytes.length === bytesLength
} catch (e) {
console.log(e)
console.error(e)
return false
}
}
export const validateAmount = async (
amount: string,
minimum: string,
): Promise<boolean> => {
export const validateAmount = async (amount: string, minimum: string): Promise<boolean> => {
// tests basic coin value requirements, like no more than 6 decimal places, value lower than total supply, etc
if (!Number(amount)) {
return false
@@ -85,11 +82,9 @@ export const validateLocation = (location: string): boolean => {
return !location.trim().includes('physical location of your node')
}
export const validateRawPort = (rawPort: number): boolean =>
!isNaN(rawPort) && rawPort >= 1 && rawPort <= 65535
export const validateRawPort = (rawPort: number): boolean => !isNaN(rawPort) && rawPort >= 1 && rawPort <= 65535
export const truncate = (text: string, trim: number) =>
text.substring(0, trim) + '...'
export const truncate = (text: string, trim: number) => text.substring(0, trim) + '...'
export const checkHasEnoughFunds = async (allocationValue: string) => {
const minorValue = await majorToMinor(allocationValue)
@@ -1,25 +1,17 @@
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FaviconsWebpackPlugin = require('favicons-webpack-plugin')
const Dotenv = require('dotenv-webpack')
var path = require('path')
module.exports = {
mode: 'development',
entry: path.resolve(__dirname, '/src/index'),
devServer: {
port: 9000,
compress: true,
historyApiFallback: true,
hot: true,
client: {
overlay: false,
},
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public/index.html'),
filename: 'index.html',
}),
new FaviconsWebpackPlugin(path.resolve(__dirname, 'public/favicon.ico')),
new Dotenv(),
],
module: {
rules: [
+17
View File
@@ -0,0 +1,17 @@
const common = require('./webpack.common')
const { merge } = require('webpack-merge')
var path = require('path')
module.exports = merge(common, {
mode: 'development',
entry: path.resolve(__dirname, '/src/index'),
devServer: {
port: 9000,
compress: true,
historyApiFallback: true,
hot: true,
client: {
overlay: false,
},
},
})
-42
View File
@@ -1,42 +0,0 @@
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FaviconsWebpackPlugin = require('favicons-webpack-plugin')
var path = require('path')
module.exports = {
mode: 'production',
node: {
__dirname: false
},
entry: path.resolve(__dirname, './src/index'),
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './public/index.html'),
filename: 'index.html',
}),
new FaviconsWebpackPlugin(path.resolve(__dirname, './public/favicon.png')),
],
module: {
rules: [
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
use: ['babel-loader'],
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|jpe?g|gif|svg)$/i,
use: [
{
loader: 'file-loader',
},
],
},
],
},
resolve: {
extensions: ['*', '.js', '.jsx', '.ts', '.tsx'],
},
}

Some files were not shown because too many files have changed in this diff Show More