Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a6ac1cf5e7 | |||
| 0ba2a3b724 | |||
| 96d5fd7612 | |||
| a1b7b43da2 | |||
| 123cbf7aff | |||
| c4d12e7a8e | |||
| bf22a13b8f | |||
| 8f5dd00027 | |||
| 84c43ebf54 | |||
| b957b939cf | |||
| 9c19ae322d | |||
| 07893828d8 | |||
| 1167f50543 | |||
| ba1818a903 | |||
| e631219a73 | |||
| 207c6cf2c7 | |||
| c5ece97872 | |||
| 8a2c95d044 | |||
| ba5e3d4efa | |||
| c81623a61a | |||
| 8bb42c2b1b | |||
| 33e161bd59 | |||
| 0233499036 | |||
| a059a29173 | |||
| 83c3398570 | |||
| 93f931459a | |||
| 5a7b19aeb6 | |||
| b901655591 | |||
| a9fdbccb82 |
@@ -1,6 +1,7 @@
|
||||
name: CI for Network Explorer
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'explorer/**'
|
||||
@@ -75,3 +76,14 @@ jobs:
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
- name: Deploy
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CD_PROD_NE_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "explorer/dist/"
|
||||
REMOTE_HOST: ${{ secrets.CD_PROD_NE_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CD_PROD_NE_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CD_PROD_NE_REMOTE_TARGET }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
|
||||
+22
-6
@@ -8,10 +8,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
### Added
|
||||
|
||||
- socks5 client/websocket client: add `--force-register-gateway` flag, useful when rerunning init ([#1353])
|
||||
- nym-connect: initial proof-of-concept of a UI around the socks5 client was added
|
||||
- nym-connect: add ability to select network requester and gateway ([#1427])
|
||||
- nym-connect: add ability to export gateway keys as JSON
|
||||
- nym-connect: add auto updater
|
||||
- all: added network compilation target to `--help` (or `--version`) commands ([#1256]).
|
||||
- explorer-api: learned how to sum the delegations by owner in a new endpoint.
|
||||
- explorer-api: add apy values to `mix_nodes` endpoint
|
||||
@@ -29,6 +25,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- validator-client: add `denom` argument and add simple test for querying an account balance
|
||||
- gateway, validator-api: Checks for coconut credential double spending attempts, taking the coconut bandwidth contract as source of truth ([#1457])
|
||||
- coconut-bandwidth-contract: Record the state of a coconut credential; create specific proposal for releasing funds ([#1457])
|
||||
- add validator-client script rust binary /tools/validator-client-scripts
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -39,10 +36,11 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- native & socks5 clients: rerun init will now reuse previous gateway configuration instead of failing ([#1353])
|
||||
- native & socks5 clients: deduplicate big chunks of init logic
|
||||
- validator: fixed local docker-compose setup to work on Apple M1 ([#1329])
|
||||
- explorer-api: listen out for SIGTERM and SIGQUIT too, making it play nicely as a system service ([#1482]).
|
||||
- network-requester: fix filter for suffix-only domains ([#1487])
|
||||
|
||||
### Changed
|
||||
|
||||
- nym-connect: reuse config id instead of creating a new id on each connection
|
||||
- validator-client: created internal `Coin` type that replaces coins from `cosmrs` and `cosmwasm` for API entrypoints [[#1295]]
|
||||
- all: updated all `cosmwasm`-related dependencies to `1.0.0` and `cw-storage-plus` to `0.13.4` [[#1318]]
|
||||
- all: updated `rocket` to `0.5.0-rc.2`.
|
||||
@@ -53,6 +51,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- validator-api: fee payment for multisig operations comes from the gateway account instead of the validator APIs' accounts ([#1419])
|
||||
- multisig-contract: Limit the proposal creating functionality to one address (coconut-bandwidth-contract address) ([#1457])
|
||||
- All binaries and cosmwasm blobs are configured at runtime now; binaries are configured using environment variables or .env files and contracts keep the configuration parameters in storage ([#1463])
|
||||
- gateway, network-statistics: include gateway id in the sent statistical data ([#1478])
|
||||
|
||||
|
||||
[#1249]: https://github.com/nymtech/nym/pull/1249
|
||||
@@ -74,9 +73,26 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#1393]: https://github.com/nymtech/nym/pull/1393
|
||||
[#1404]: https://github.com/nymtech/nym/pull/1404
|
||||
[#1419]: https://github.com/nymtech/nym/pull/1419
|
||||
[#1427]: https://github.com/nymtech/nym/pull/1427
|
||||
[#1457]: https://github.com/nymtech/nym/pull/1457
|
||||
[#1463]: https://github.com/nymtech/nym/pull/1463
|
||||
[#1478]: https://github.com/nymtech/nym/pull/1478
|
||||
[#1482]: https://github.com/nymtech/nym/pull/1482
|
||||
[#1487]: https://github.com/nymtech/nym/pull/1487
|
||||
|
||||
## [nym-connect-v1.0.1](https://github.com/nymtech/nym/tree/nym-connect-v1.0.1) (2022-07-22)
|
||||
|
||||
### Added
|
||||
|
||||
- nym-connect: initial proof-of-concept of a UI around the socks5 client was added
|
||||
- nym-connect: add ability to select network requester and gateway ([#1427])
|
||||
- nym-connect: add ability to export gateway keys as JSON
|
||||
- nym-connect: add auto updater
|
||||
|
||||
### Changed
|
||||
|
||||
- nym-connect: reuse config id instead of creating a new id on each connection
|
||||
|
||||
[#1427]: https://github.com/nymtech/nym/pull/1427
|
||||
|
||||
## [nym-wallet-v1.0.7](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.7) (2022-07-11)
|
||||
|
||||
|
||||
Generated
+33
@@ -1606,6 +1606,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"task",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"validator-client",
|
||||
@@ -5396,6 +5397,15 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "streaming-stats"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0d670ce4e348a2081843569e0f79b21c99c91bb9028b3b3ecb0f050306de547"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stringprep"
|
||||
version = "0.1.2"
|
||||
@@ -6283,6 +6293,29 @@ dependencies = [
|
||||
"vesting-contract-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "validator-client-scripts"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bip39",
|
||||
"bs58",
|
||||
"clap 3.2.8",
|
||||
"csv",
|
||||
"dotenv",
|
||||
"log",
|
||||
"mixnet-contract-common",
|
||||
"network-defaults",
|
||||
"pretty_env_logger",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"streaming-stats",
|
||||
"tokio",
|
||||
"url",
|
||||
"validator-client",
|
||||
"vesting-contract-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
|
||||
+2
-1
@@ -68,7 +68,8 @@ members = [
|
||||
"service-providers/network-statistics",
|
||||
"validator-api",
|
||||
"validator-api/validator-api-requests",
|
||||
"tools/ts-rs-cli"
|
||||
"tools/ts-rs-cli",
|
||||
"tools/validator-client-scripts"
|
||||
]
|
||||
|
||||
default-members = [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
test: clippy-all cargo-test wasm fmt
|
||||
test-all: test cargo-test-expensive
|
||||
test: build clippy-all cargo-test wasm fmt
|
||||
no-clippy: build cargo-test wasm fmt
|
||||
happy: fmt clippy-happy test
|
||||
clippy-all: clippy-all-main clippy-all-contracts clippy-all-wallet clippy-all-connect
|
||||
@@ -69,6 +69,9 @@ build-wallet:
|
||||
build-connect:
|
||||
cargo build --manifest-path nym-connect/Cargo.toml --workspace
|
||||
|
||||
build-validator-scripts:
|
||||
cargo build --release --manifest-path tools/validator-client-scripts/Cargo.toml
|
||||
|
||||
fmt-main:
|
||||
cargo fmt --all
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ pub(crate) async fn execute(args: &Cli) {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_validators(raw: &str) -> Vec<Url> {
|
||||
pub fn parse_validators(raw: &str) -> Vec<Url> {
|
||||
raw.split(',')
|
||||
.map(|raw_validator| {
|
||||
raw_validator
|
||||
|
||||
@@ -5,7 +5,7 @@ use futures::StreamExt;
|
||||
use log::*;
|
||||
use nymsphinx::receiver::ReconstructedMessage;
|
||||
use proxy_helpers::connection_controller::{ControllerCommand, ControllerSender};
|
||||
use socks5_requests::Response;
|
||||
use socks5_requests::Message;
|
||||
|
||||
pub(crate) struct MixnetResponseListener {
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
@@ -44,12 +44,16 @@ impl MixnetResponseListener {
|
||||
warn!("this message had a surb - we didn't do anything with it");
|
||||
}
|
||||
|
||||
let response = match Response::try_from_bytes(&raw_message) {
|
||||
let response = match Message::try_from_bytes(&raw_message) {
|
||||
Err(err) => {
|
||||
warn!("failed to parse received response - {:?}", err);
|
||||
return;
|
||||
}
|
||||
Ok(data) => data,
|
||||
Ok(Message::Request(_)) => {
|
||||
warn!("unexpected request");
|
||||
return;
|
||||
}
|
||||
Ok(Message::Response(data)) => data,
|
||||
};
|
||||
|
||||
self.controller_sender
|
||||
|
||||
@@ -1006,6 +1006,29 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
#[execute("mixnet")]
|
||||
fn _compound_reward(
|
||||
&self,
|
||||
operator: Option<String>,
|
||||
delegator: Option<String>,
|
||||
mix_identity: Option<IdentityKey>,
|
||||
proxy: Option<String>,
|
||||
fee: Option<Fee>,
|
||||
) -> (ExecuteMsg, Option<Fee>)
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
(
|
||||
ExecuteMsg::CompoundReward {
|
||||
operator,
|
||||
delegator,
|
||||
mix_identity,
|
||||
proxy,
|
||||
},
|
||||
fee,
|
||||
)
|
||||
}
|
||||
|
||||
#[execute("mixnet")]
|
||||
fn _compound_operator_reward(&self, fee: Option<Fee>) -> (ExecuteMsg, Option<Fee>)
|
||||
where
|
||||
|
||||
@@ -33,6 +33,12 @@ pub enum ExecuteMsg {
|
||||
CompoundDelegatorReward {
|
||||
mix_identity: IdentityKey,
|
||||
},
|
||||
CompoundReward {
|
||||
operator: Option<String>,
|
||||
delegator: Option<String>,
|
||||
mix_identity: Option<IdentityKey>,
|
||||
proxy: Option<String>,
|
||||
},
|
||||
BondMixnode {
|
||||
mix_node: MixNode,
|
||||
owner_signature: String,
|
||||
@@ -116,6 +122,7 @@ pub enum ExecuteMsg {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum QueryMsg {
|
||||
GetBlacklistedNodes {},
|
||||
GetCurrentOperatorCost {},
|
||||
GetRewardingValidatorAddress {},
|
||||
GetAllDelegationKeys {},
|
||||
@@ -206,6 +213,31 @@ pub enum QueryMsg {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct MigrateMsg {}
|
||||
pub struct MigrateMsg {
|
||||
pub mixnet_denom: String,
|
||||
nodes_to_remove: Option<Vec<NodeToRemove>>,
|
||||
}
|
||||
|
||||
impl MigrateMsg {
|
||||
pub fn nodes_to_remove(&self) -> Vec<NodeToRemove> {
|
||||
self.nodes_to_remove.clone().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct NodeToRemove {
|
||||
owner: String,
|
||||
proxy: Option<String>,
|
||||
}
|
||||
|
||||
impl NodeToRemove {
|
||||
pub fn owner(&self) -> &str {
|
||||
&self.owner
|
||||
}
|
||||
|
||||
pub fn proxy(&self) -> Option<&String> {
|
||||
self.proxy.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@ pub struct InitMsg {
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct MigrateMsg {}
|
||||
pub struct MigrateMsg {
|
||||
pub mix_denom: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)]
|
||||
pub struct VestingSpecification {
|
||||
|
||||
@@ -34,12 +34,16 @@ pub enum StatsData {
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct StatsGatewayData {
|
||||
pub gateway_id: String,
|
||||
pub inbox_count: u32,
|
||||
}
|
||||
|
||||
impl StatsGatewayData {
|
||||
pub fn new(inbox_count: u32) -> Self {
|
||||
StatsGatewayData { inbox_count }
|
||||
pub fn new(gateway_id: String, inbox_count: u32) -> Self {
|
||||
StatsGatewayData {
|
||||
gateway_id,
|
||||
inbox_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Generated
+4
-4
@@ -1392,18 +1392,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.4"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||
dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.25"
|
||||
version = "0.6.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||
|
||||
[[package]]
|
||||
name = "rfc6979"
|
||||
|
||||
@@ -27,16 +27,18 @@ use crate::mixnodes::bonding_queries::{
|
||||
query_checkpoints_for_mixnode, query_mixnode_at_height, query_mixnodes_paged,
|
||||
};
|
||||
use crate::mixnodes::layer_queries::query_layer_distribution;
|
||||
use crate::mixnodes::transactions::_try_remove_mixnode;
|
||||
use crate::queued_migrations::migrate_config_from_env;
|
||||
use crate::rewards::queries::{
|
||||
query_circulating_supply, query_reward_pool, query_rewarding_status, query_staking_supply,
|
||||
};
|
||||
use crate::rewards::storage as rewards_storage;
|
||||
use cosmwasm_std::{
|
||||
entry_point, to_binary, Addr, Api, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
|
||||
Uint128,
|
||||
Storage, Uint128,
|
||||
};
|
||||
use mixnet_contract_common::{
|
||||
ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
|
||||
ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, NodeToRemove, QueryMsg,
|
||||
};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
@@ -112,6 +114,19 @@ pub fn execute(
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::CompoundReward {
|
||||
operator,
|
||||
delegator,
|
||||
mix_identity,
|
||||
proxy,
|
||||
} => crate::rewards::transactions::try_compound_reward(
|
||||
deps,
|
||||
env,
|
||||
operator,
|
||||
delegator,
|
||||
mix_identity,
|
||||
proxy,
|
||||
),
|
||||
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
|
||||
try_update_rewarding_validator_address(deps, info, address)
|
||||
}
|
||||
@@ -127,7 +142,7 @@ pub fn execute(
|
||||
owner_signature,
|
||||
),
|
||||
ExecuteMsg::UnbondMixnode {} => {
|
||||
crate::mixnodes::transactions::try_remove_mixnode(env, deps, info)
|
||||
crate::mixnodes::transactions::try_remove_mixnode(&env, deps.storage, deps.api, info)
|
||||
}
|
||||
ExecuteMsg::UpdateMixnodeConfig {
|
||||
profit_margin_percent,
|
||||
@@ -221,7 +236,13 @@ pub fn execute(
|
||||
owner_signature,
|
||||
),
|
||||
ExecuteMsg::UnbondMixnodeOnBehalf { owner } => {
|
||||
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(env, deps, info, owner)
|
||||
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(
|
||||
&env,
|
||||
deps.storage,
|
||||
deps.api,
|
||||
info,
|
||||
owner,
|
||||
)
|
||||
}
|
||||
ExecuteMsg::BondGatewayOnBehalf {
|
||||
gateway,
|
||||
@@ -280,7 +301,7 @@ pub fn execute(
|
||||
)
|
||||
}
|
||||
ExecuteMsg::ReconcileDelegations {} => {
|
||||
crate::delegations::transactions::try_reconcile_all_delegation_events(deps, info)
|
||||
crate::delegations::transactions::try_reconcile_all_delegation_events(deps)
|
||||
}
|
||||
ExecuteMsg::CheckpointMixnodes {} => {
|
||||
crate::mixnodes::transactions::try_checkpoint_mixnodes(
|
||||
@@ -321,6 +342,9 @@ pub fn execute(
|
||||
#[entry_point]
|
||||
pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
|
||||
let query_res = match msg {
|
||||
QueryMsg::GetBlacklistedNodes {} => to_binary(
|
||||
&crate::mixnodes::bonding_queries::get_blacklisted_nodes(deps),
|
||||
),
|
||||
QueryMsg::GetRewardingValidatorAddress {} => {
|
||||
to_binary(&query_rewarding_validator_address(deps)?)
|
||||
}
|
||||
@@ -448,9 +472,54 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
|
||||
Ok(query_res?)
|
||||
}
|
||||
|
||||
fn blacklist_malicious_node(storage: &mut dyn Storage, owner: &Addr) -> Result<(), ContractError> {
|
||||
let mixnode_bond = match crate::mixnodes::storage::mixnodes()
|
||||
.idx
|
||||
.owner
|
||||
.item(storage, owner.clone())?
|
||||
{
|
||||
Some(record) => record.1,
|
||||
None => {
|
||||
return Err(ContractError::NoAssociatedMixNodeBond {
|
||||
owner: owner.to_owned(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
crate::mixnodes::storage::MIXNODES_BOND_BLACKLIST.save(storage, mixnode_bond.identity(), &0)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Removes nodes we've deemed malicious, returns the pledge to the owners, but does not send any rewards
|
||||
fn remove_malicious_node(
|
||||
storage: &mut dyn Storage,
|
||||
api: &dyn Api,
|
||||
env: &Env,
|
||||
node: &NodeToRemove,
|
||||
) -> Result<Response, ContractError> {
|
||||
let proxy = node.proxy().map(|p| {
|
||||
api.addr_validate(p)
|
||||
.unwrap_or_else(|_| panic!("Invalid address: {}", p))
|
||||
});
|
||||
let owner_addr = api.addr_validate(node.owner())?;
|
||||
blacklist_malicious_node(storage, &owner_addr)?;
|
||||
_try_remove_mixnode(env, storage, api, node.owner(), proxy, false)
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
Ok(Default::default())
|
||||
pub fn migrate(deps: DepsMut<'_>, env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
migrate_config_from_env(deps.storage, &msg)?;
|
||||
let mut response = Response::new();
|
||||
for node in msg.nodes_to_remove().iter() {
|
||||
let mut sub_response = remove_malicious_node(deps.storage, deps.api, &env, node)
|
||||
.unwrap_or_else(|_| panic!("Could not remove node: {:?}", node));
|
||||
response.messages.append(&mut sub_response.messages);
|
||||
response.attributes.append(&mut sub_response.attributes);
|
||||
response.events.append(&mut sub_response.events);
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -19,16 +19,7 @@ use mixnet_contract_common::{Delegation, IdentityKey};
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
use vesting_contract_common::one_ucoin;
|
||||
|
||||
pub fn try_reconcile_all_delegation_events(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, ContractError> {
|
||||
let state = mixnet_params_storage::CONTRACT_STATE.load(deps.storage)?;
|
||||
// check if this is executed by the permitted validator, if not reject the transaction
|
||||
if info.sender != state.rewarding_validator_address {
|
||||
return Err(ContractError::Unauthorized);
|
||||
}
|
||||
|
||||
pub fn try_reconcile_all_delegation_events(deps: DepsMut<'_>) -> Result<Response, ContractError> {
|
||||
_try_reconcile_all_delegation_events(deps.storage, deps.api)
|
||||
}
|
||||
|
||||
@@ -630,7 +621,14 @@ mod tests {
|
||||
deps.as_mut(),
|
||||
);
|
||||
let delegation_owner = Addr::unchecked("sender");
|
||||
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
let api = deps.api.clone();
|
||||
try_remove_mixnode(
|
||||
&mock_env(),
|
||||
deps.as_mut().storage,
|
||||
&api,
|
||||
mock_info(mixnode_owner, &[]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Err(ContractError::MixNodeBondNotFound {
|
||||
identity: identity.clone()
|
||||
@@ -653,7 +651,14 @@ mod tests {
|
||||
tests::fixtures::good_mixnode_pledge(),
|
||||
deps.as_mut(),
|
||||
);
|
||||
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
let api = deps.api.clone();
|
||||
try_remove_mixnode(
|
||||
&mock_env(),
|
||||
deps.as_mut().storage,
|
||||
&api,
|
||||
mock_info(mixnode_owner, &[]),
|
||||
)
|
||||
.unwrap();
|
||||
let identity = test_helpers::add_mixnode(
|
||||
mixnode_owner,
|
||||
tests::fixtures::good_mixnode_pledge(),
|
||||
@@ -917,7 +922,14 @@ mod tests {
|
||||
identity.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
let api = deps.api.clone();
|
||||
try_remove_mixnode(
|
||||
&mock_env(),
|
||||
deps.as_mut().storage,
|
||||
&api,
|
||||
mock_info(mixnode_owner, &[]),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Err(ContractError::MixNodeBondNotFound {
|
||||
identity: identity.clone()
|
||||
@@ -1058,8 +1070,14 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
let api = deps.api.clone();
|
||||
try_remove_mixnode(
|
||||
&mock_env(),
|
||||
deps.as_mut().storage,
|
||||
&api,
|
||||
mock_info(mixnode_owner, &[]),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected = Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
@@ -1084,179 +1102,13 @@ mod tests {
|
||||
|
||||
#[cfg(test)]
|
||||
mod removing_mix_stake_delegation {
|
||||
use super::*;
|
||||
use crate::support::tests;
|
||||
use cosmwasm_std::coin;
|
||||
use cosmwasm_std::testing::mock_env;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::Addr;
|
||||
|
||||
use crate::support::tests;
|
||||
|
||||
use super::*;
|
||||
|
||||
// TODO: Probably delete due to reconciliation logic
|
||||
//#[ignore]
|
||||
//#[test]
|
||||
//fn fails_if_delegation_never_existed() {
|
||||
// let mut deps = test_helpers::init_contract();
|
||||
// let env = mock_env();
|
||||
// let mixnode_owner = "bob";
|
||||
// let identity = test_helpers::add_mixnode(
|
||||
// mixnode_owner,
|
||||
// tests::fixtures::good_mixnode_pledge(),
|
||||
// deps.as_mut(),
|
||||
// );
|
||||
// let delegation_owner = Addr::unchecked("sender");
|
||||
// assert_eq!(
|
||||
// Err(ContractError::NoMixnodeDelegationFound {
|
||||
// identity: identity.clone(),
|
||||
// address: delegation_owner.to_string(),
|
||||
// }),
|
||||
// try_remove_delegation_from_mixnode(
|
||||
// deps.as_mut(),
|
||||
// env,
|
||||
// mock_info(delegation_owner.as_str(), &[]),
|
||||
// identity,
|
||||
// )
|
||||
// );
|
||||
//}
|
||||
|
||||
// TODO: Update to work with reconciliation
|
||||
//#[ignore]
|
||||
//#[test]
|
||||
//fn succeeds_if_delegation_existed() {
|
||||
// let mut deps = test_helpers::init_contract();
|
||||
// let mixnode_owner = "bob";
|
||||
// let env = mock_env();
|
||||
// let identity = test_helpers::add_mixnode(
|
||||
// mixnode_owner,
|
||||
// tests::fixtures::good_mixnode_pledge(),
|
||||
// deps.as_mut(),
|
||||
// );
|
||||
// let delegation_owner = Addr::unchecked("sender");
|
||||
// try_delegate_to_mixnode(
|
||||
// deps.as_mut(),
|
||||
// mock_env(),
|
||||
// mock_info(delegation_owner.as_str(), &coins(100, TEST_COIN_DENOM)),
|
||||
// identity.clone(),
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
// let _delegation = query_mixnode_delegation(
|
||||
// &deps.storage,
|
||||
// &deps.api,
|
||||
// identity.clone(),
|
||||
// delegation_owner.clone().into_string(),
|
||||
// None,
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// let expected_response = Response::new()
|
||||
// .add_message(BankMsg::Send {
|
||||
// to_address: delegation_owner.clone().into(),
|
||||
// amount: coins(100, TEST_COIN_DENOM),
|
||||
// })
|
||||
// .add_event(new_undelegation_event(
|
||||
// &delegation_owner,
|
||||
// &None,
|
||||
// &identity,
|
||||
// Uint128::new(100),
|
||||
// ));
|
||||
|
||||
// assert_eq!(
|
||||
// Ok(expected_response),
|
||||
// try_remove_delegation_from_mixnode(
|
||||
// deps.as_mut(),
|
||||
// env,
|
||||
// mock_info(delegation_owner.as_str(), &[]),
|
||||
// identity.clone(),
|
||||
// )
|
||||
// );
|
||||
// assert!(storage::delegations()
|
||||
// .may_load(
|
||||
// &deps.storage,
|
||||
// (identity.clone(), delegation_owner.as_bytes().to_vec(), 0),
|
||||
// )
|
||||
// .unwrap()
|
||||
// .is_none());
|
||||
|
||||
// // and total delegation is cleared
|
||||
// assert_eq!(
|
||||
// Uint128::zero(),
|
||||
// mixnodes_storage::TOTAL_DELEGATION
|
||||
// .load(&deps.storage, &identity)
|
||||
// .unwrap()
|
||||
// )
|
||||
//}
|
||||
|
||||
// TODO: Update to work with reconciliation
|
||||
//#[ignore]
|
||||
//#[test]
|
||||
//fn succeeds_if_delegation_existed_even_if_node_unbonded() {
|
||||
// let mut deps = test_helpers::init_contract();
|
||||
// let mixnode_owner = "bob";
|
||||
// let env = mock_env();
|
||||
// let identity = test_helpers::add_mixnode(
|
||||
// mixnode_owner,
|
||||
// tests::fixtures::good_mixnode_pledge(),
|
||||
// deps.as_mut(),
|
||||
// );
|
||||
// let delegation_owner = Addr::unchecked("sender");
|
||||
// try_delegate_to_mixnode(
|
||||
// deps.as_mut(),
|
||||
// mock_env(),
|
||||
// mock_info(delegation_owner.as_str(), &coins(100, TEST_COIN_DENOM)),
|
||||
// identity.clone(),
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
// let _delegation = query_mixnode_delegation(
|
||||
// &deps.storage,
|
||||
// &deps.api,
|
||||
// identity.clone(),
|
||||
// delegation_owner.clone().into_string(),
|
||||
// None,
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// let expected_response = Response::new()
|
||||
// .add_message(BankMsg::Send {
|
||||
// to_address: delegation_owner.clone().into(),
|
||||
// amount: coins(100, TEST_COIN_DENOM),
|
||||
// })
|
||||
// .add_event(new_undelegation_event(
|
||||
// &delegation_owner,
|
||||
// &None,
|
||||
// &identity,
|
||||
// Uint128::new(100),
|
||||
// ));
|
||||
|
||||
// try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
|
||||
// assert_eq!(
|
||||
// Ok(expected_response),
|
||||
// try_remove_delegation_from_mixnode(
|
||||
// deps.as_mut(),
|
||||
// env,
|
||||
// mock_info(delegation_owner.as_str(), &[]),
|
||||
// identity.clone(),
|
||||
// )
|
||||
// );
|
||||
|
||||
// _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
// assert!(test_helpers::read_delegation(
|
||||
// &deps.storage,
|
||||
// identity,
|
||||
// delegation_owner.as_bytes(),
|
||||
// mock_env().block.height
|
||||
// )
|
||||
// .is_none());
|
||||
//}
|
||||
|
||||
#[test]
|
||||
fn total_delegation_is_preserved_if_only_some_undelegate() {
|
||||
let mut deps = test_helpers::init_contract();
|
||||
|
||||
@@ -178,4 +178,11 @@ pub enum ContractError {
|
||||
last_update_time: u64,
|
||||
current_block_time: u64,
|
||||
},
|
||||
#[error("`mix_identity` is required when `delegator` is set")]
|
||||
MissingMixIdentity,
|
||||
#[error("Compounding has been disabled temporarily")]
|
||||
CompoundDisabled,
|
||||
|
||||
#[error("Mixnode {identity} has been blacklisted on the network")]
|
||||
MixnodeBlacklisted { identity: String },
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@ mod gateways;
|
||||
mod interval;
|
||||
mod mixnet_contract_settings;
|
||||
mod mixnodes;
|
||||
mod queued_migrations;
|
||||
mod rewards;
|
||||
mod support;
|
||||
|
||||
@@ -8,6 +8,13 @@ use mixnet_contract_common::{
|
||||
IdentityKey, MixNodeBond, MixOwnershipResponse, MixnodeBondResponse, PagedMixnodeResponse,
|
||||
};
|
||||
|
||||
pub fn get_blacklisted_nodes(deps: Deps<'_>) -> Vec<IdentityKey> {
|
||||
storage::MIXNODES_BOND_BLACKLIST
|
||||
.keys(deps.storage, None, None, Order::Ascending)
|
||||
.filter_map(|i| i.ok())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn query_mixnode_at_height(
|
||||
deps: Deps<'_>,
|
||||
mix_identity: String,
|
||||
@@ -252,10 +259,13 @@ pub(crate) mod tests {
|
||||
let res = query_owns_mixnode(deps.as_ref(), "fred".to_string()).unwrap();
|
||||
assert!(res.mixnode.is_some());
|
||||
|
||||
let api = deps.api.clone();
|
||||
|
||||
// but after unbonding it, he doesn't own one anymore
|
||||
crate::mixnodes::transactions::try_remove_mixnode(
|
||||
env,
|
||||
deps.as_mut(),
|
||||
&env,
|
||||
deps.as_mut().storage,
|
||||
&api,
|
||||
mock_info("fred", &[]),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -19,6 +19,7 @@ const MIXNODES_PK_CHECKPOINTS: &str = "mn__check";
|
||||
const MIXNODES_PK_CHANGELOG: &str = "mn__change";
|
||||
const MIXNODES_OWNER_IDX_NAMESPACE: &str = "mno";
|
||||
const MIXNODES_SPHINX_IDX_NAMESPACE: &str = "mns";
|
||||
const MIXNODES_BOND_BLACKLIST_NAMESPACE: &str = "mbb";
|
||||
|
||||
const LAST_PM_UPDATE_NAMESPACE: &str = "lpm";
|
||||
|
||||
@@ -32,6 +33,9 @@ pub(crate) const TOTAL_DELEGATION: Map<'_, IdentityKeyRef<'_>, Uint128> =
|
||||
pub(crate) const LAST_PM_UPDATE_TIME: Map<'_, IdentityKeyRef<'_>, u64> =
|
||||
Map::new(LAST_PM_UPDATE_NAMESPACE);
|
||||
|
||||
pub(crate) const MIXNODES_BOND_BLACKLIST: Map<'_, IdentityKeyRef<'_>, u8> =
|
||||
Map::new(MIXNODES_BOND_BLACKLIST_NAMESPACE);
|
||||
|
||||
pub(crate) struct MixnodeBondIndex<'a> {
|
||||
pub(crate) owner: UniqueIndex<'a, Addr, StoredMixnodeBond>,
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@ use crate::mixnodes::layer_queries::query_layer_distribution;
|
||||
use crate::mixnodes::storage::StoredMixnodeBond;
|
||||
use crate::support::helpers::{ensure_no_existing_bond, validate_node_identity_signature};
|
||||
use cosmwasm_std::{
|
||||
wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Storage, Uint128,
|
||||
wasm_execute, Addr, Api, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Storage, Uint128,
|
||||
};
|
||||
use mixnet_contract_common::events::{
|
||||
new_checkpoint_mixnodes_event, new_mixnode_bonding_event, new_mixnode_unbonding_event,
|
||||
};
|
||||
use mixnet_contract_common::MixNode;
|
||||
use mixnet_contract_common::{IdentityKeyRef, MixNode};
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
use vesting_contract_common::one_ucoin;
|
||||
|
||||
@@ -87,6 +87,15 @@ pub fn try_add_mixnode_on_behalf(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_blacklisted(
|
||||
storage: &dyn Storage,
|
||||
identity: IdentityKeyRef,
|
||||
) -> Result<bool, ContractError> {
|
||||
Ok(storage::MIXNODES_BOND_BLACKLIST
|
||||
.may_load(storage, identity)?
|
||||
.is_some())
|
||||
}
|
||||
|
||||
fn _try_add_mixnode(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
@@ -100,6 +109,12 @@ fn _try_add_mixnode(
|
||||
// if the client has an active bonded mixnode or gateway, don't allow bonding
|
||||
ensure_no_existing_bond(deps.storage, &owner)?;
|
||||
|
||||
if is_blacklisted(deps.storage, &mix_node.identity_key)? {
|
||||
return Err(ContractError::MixnodeBlacklisted {
|
||||
identity: mix_node.identity_key,
|
||||
});
|
||||
};
|
||||
|
||||
// We don't have to check lower bound as its an u8
|
||||
if mix_node.profit_margin_percent > 100 {
|
||||
return Err(ContractError::InvalidProfitMarginPercent(
|
||||
@@ -167,45 +182,47 @@ fn _try_add_mixnode(
|
||||
}
|
||||
|
||||
pub fn try_remove_mixnode_on_behalf(
|
||||
env: Env,
|
||||
deps: DepsMut<'_>,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
api: &dyn Api,
|
||||
info: MessageInfo,
|
||||
owner: String,
|
||||
) -> Result<Response, ContractError> {
|
||||
let proxy = info.sender;
|
||||
_try_remove_mixnode(env, deps, &owner, Some(proxy))
|
||||
_try_remove_mixnode(env, storage, api, &owner, Some(proxy), true)
|
||||
}
|
||||
|
||||
pub fn try_remove_mixnode(
|
||||
env: Env,
|
||||
deps: DepsMut<'_>,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
api: &dyn Api,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, ContractError> {
|
||||
_try_remove_mixnode(env, deps, info.sender.as_ref(), None)
|
||||
_try_remove_mixnode(env, storage, api, info.sender.as_ref(), None, true)
|
||||
}
|
||||
|
||||
pub(crate) fn _try_remove_mixnode(
|
||||
env: Env,
|
||||
deps: DepsMut<'_>,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
api: &dyn Api,
|
||||
owner: &str,
|
||||
proxy: Option<Addr>,
|
||||
collect_rewards: bool,
|
||||
) -> Result<Response, ContractError> {
|
||||
let owner = deps.api.addr_validate(owner)?;
|
||||
let owner = api.addr_validate(owner)?;
|
||||
|
||||
crate::rewards::transactions::_try_compound_operator_reward(
|
||||
deps.storage,
|
||||
deps.api,
|
||||
env.block.height,
|
||||
&owner,
|
||||
None,
|
||||
)?;
|
||||
if collect_rewards {
|
||||
crate::rewards::transactions::_try_compound_operator_reward(
|
||||
storage,
|
||||
api,
|
||||
env.block.height,
|
||||
&owner,
|
||||
None,
|
||||
)?;
|
||||
}
|
||||
|
||||
// try to find the node of the sender
|
||||
let mixnode_bond = match storage::mixnodes()
|
||||
.idx
|
||||
.owner
|
||||
.item(deps.storage, owner.clone())?
|
||||
{
|
||||
let mixnode_bond = match storage::mixnodes().idx.owner.item(storage, owner.clone())? {
|
||||
Some(record) => record.1,
|
||||
None => return Err(ContractError::NoAssociatedMixNodeBond { owner }),
|
||||
};
|
||||
@@ -225,10 +242,10 @@ pub(crate) fn _try_remove_mixnode(
|
||||
};
|
||||
|
||||
// remove the bond
|
||||
storage::mixnodes().remove(deps.storage, mixnode_bond.identity(), env.block.height)?;
|
||||
storage::mixnodes().remove(storage, mixnode_bond.identity(), env.block.height)?;
|
||||
|
||||
// decrement layer count
|
||||
mixnet_params_storage::decrement_layer_count(deps.storage, mixnode_bond.layer)?;
|
||||
mixnet_params_storage::decrement_layer_count(storage, mixnode_bond.layer)?;
|
||||
|
||||
let mut response = Response::new();
|
||||
|
||||
@@ -238,8 +255,7 @@ pub(crate) fn _try_remove_mixnode(
|
||||
amount: mixnode_bond.pledge_amount(),
|
||||
};
|
||||
|
||||
let track_unbond_message =
|
||||
wasm_execute(proxy, &msg, vec![one_ucoin(mix_denom(deps.storage)?)])?;
|
||||
let track_unbond_message = wasm_execute(proxy, &msg, vec![one_ucoin(mix_denom(storage)?)])?;
|
||||
response = response.add_message(track_unbond_message);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{Addr, Response, Storage};
|
||||
use cw_storage_plus::Item;
|
||||
use mixnet_contract_common::{ContractStateParams, MigrateMsg};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::ContractError;
|
||||
use crate::mixnet_contract_settings::models::ContractState;
|
||||
use crate::mixnet_contract_settings::storage::CONTRACT_STATE;
|
||||
|
||||
pub fn migrate_config_from_env(
|
||||
storage: &mut dyn Storage,
|
||||
msg: &MigrateMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct OldContractState {
|
||||
pub owner: Addr,
|
||||
pub mix_denom: String,
|
||||
pub rewarding_validator_address: Addr,
|
||||
pub params: ContractStateParams,
|
||||
}
|
||||
const OLD_CONTRACT_STATE: Item<'_, OldContractState> = Item::new("config");
|
||||
|
||||
let old_state = OLD_CONTRACT_STATE.load(storage)?;
|
||||
let new_state = ContractState {
|
||||
owner: old_state.owner,
|
||||
mix_denom: msg.mixnet_denom.clone(),
|
||||
rewarding_validator_address: old_state.rewarding_validator_address,
|
||||
params: old_state.params,
|
||||
};
|
||||
|
||||
CONTRACT_STATE.save(storage, &new_state)?;
|
||||
|
||||
Ok(Default::default())
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use crate::error::ContractError;
|
||||
use crate::mixnet_contract_settings::storage::mix_denom;
|
||||
use crate::mixnodes::storage::mixnodes;
|
||||
use crate::mixnodes::storage::{self as mixnodes_storage, StoredMixnodeBond};
|
||||
use crate::mixnodes::transactions::is_blacklisted;
|
||||
use crate::rewards::helpers;
|
||||
use crate::support::helpers::{is_authorized, operator_cost_at_epoch};
|
||||
use cosmwasm_std::{
|
||||
@@ -372,6 +373,56 @@ pub fn try_compound_delegator_reward_on_behalf(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn try_compound_reward(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
operator: Option<String>,
|
||||
delegator: Option<String>,
|
||||
mix_identity: Option<IdentityKey>,
|
||||
proxy: Option<String>,
|
||||
) -> Result<Response, ContractError> {
|
||||
let proxy = proxy.and_then(|p| deps.api.addr_validate(&p).ok());
|
||||
if let Some(operator_address) = operator {
|
||||
let operator_address = deps.api.addr_validate(&operator_address)?;
|
||||
let reward = _try_compound_operator_reward(
|
||||
deps.storage,
|
||||
deps.api,
|
||||
env.block.height,
|
||||
&operator_address,
|
||||
proxy,
|
||||
)?;
|
||||
Ok(
|
||||
Response::default().add_event(new_compound_operator_reward_event(
|
||||
&operator_address,
|
||||
reward,
|
||||
)),
|
||||
)
|
||||
} else if let Some(delegator_address) = delegator {
|
||||
if mix_identity.is_none() {
|
||||
return Err(ContractError::MissingMixIdentity);
|
||||
}
|
||||
|
||||
let delegator_address = deps.api.addr_validate(&delegator_address)?;
|
||||
let reward = _try_compound_delegator_reward(
|
||||
env.block.height,
|
||||
deps,
|
||||
delegator_address.as_str(),
|
||||
mix_identity.as_ref().unwrap(),
|
||||
proxy,
|
||||
)?;
|
||||
Ok(
|
||||
Response::default().add_event(new_compound_delegator_reward_event(
|
||||
&delegator_address,
|
||||
&None,
|
||||
reward,
|
||||
&mix_identity.unwrap(),
|
||||
)),
|
||||
)
|
||||
} else {
|
||||
Ok(Response::default())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_compound_delegator_reward(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
@@ -412,7 +463,7 @@ pub fn _try_compound_delegator_reward(
|
||||
proxy.as_ref(),
|
||||
);
|
||||
let reward = calculate_delegator_reward(deps.storage, deps.api, key.clone(), mix_identity)?;
|
||||
let mut compounded_delegation = reward;
|
||||
let mut total_delegation_delegate = Uint128::zero();
|
||||
|
||||
// Might want to introduce paging here
|
||||
let delegation_heights = delegation_map
|
||||
@@ -424,7 +475,7 @@ pub fn _try_compound_delegator_reward(
|
||||
for h in delegation_heights {
|
||||
let delegation =
|
||||
delegation_map.load(deps.storage, (mix_identity.to_string(), key.clone(), h))?;
|
||||
compounded_delegation += delegation.amount.amount;
|
||||
total_delegation_delegate += delegation.amount.amount;
|
||||
delegation_map.replace(
|
||||
deps.storage,
|
||||
(mix_identity.to_string(), key.clone(), h),
|
||||
@@ -433,7 +484,22 @@ pub fn _try_compound_delegator_reward(
|
||||
)?;
|
||||
}
|
||||
|
||||
let compounded_delegation = total_delegation_delegate + reward;
|
||||
|
||||
if compounded_delegation != Uint128::zero() {
|
||||
mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>(
|
||||
deps.storage,
|
||||
mix_identity,
|
||||
|total_delegation| {
|
||||
// since we know that the target node exists and because the total_delegation bucket
|
||||
// entry is created whenever the node itself is added, the unwrap here is fine
|
||||
// as the entry MUST exist
|
||||
Ok(total_delegation
|
||||
.unwrap()
|
||||
.saturating_sub(total_delegation_delegate))
|
||||
},
|
||||
)?;
|
||||
|
||||
_try_delegate_to_mixnode(
|
||||
deps.branch(),
|
||||
block_height,
|
||||
@@ -472,6 +538,10 @@ pub fn calculate_delegator_reward(
|
||||
key: Vec<u8>,
|
||||
mix_identity: &str,
|
||||
) -> Result<Uint128, ContractError> {
|
||||
if is_blacklisted(storage, mix_identity)? {
|
||||
return Ok(Uint128::zero());
|
||||
};
|
||||
|
||||
let last_claimed_height = storage::DELEGATOR_REWARD_CLAIMED_HEIGHT
|
||||
.load(storage, (key.clone(), mix_identity.to_string()))
|
||||
.unwrap_or(0);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::errors::ContractError;
|
||||
use crate::queued_migrations::migrate_config_from_env;
|
||||
use crate::storage::{
|
||||
account_from_address, locked_pledge_cap, update_locked_pledge_cap, ADMIN,
|
||||
MIXNET_CONTRACT_ADDRESS, MIX_DENOM,
|
||||
@@ -41,6 +42,7 @@ pub fn instantiate(
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
migrate_config_from_env(_deps, _env, _msg)?;
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod contract;
|
||||
mod errors;
|
||||
mod queued_migrations;
|
||||
mod storage;
|
||||
mod support;
|
||||
mod traits;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{DepsMut, Env, Response};
|
||||
use vesting_contract_common::MigrateMsg;
|
||||
|
||||
use crate::{errors::ContractError, storage::MIX_DENOM};
|
||||
|
||||
pub fn migrate_config_from_env(
|
||||
deps: DepsMut<'_>,
|
||||
_env: Env,
|
||||
msg: MigrateMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
MIX_DENOM.save(deps.storage, &msg.mix_denom)?;
|
||||
|
||||
Ok(Default::default())
|
||||
}
|
||||
@@ -26,4 +26,5 @@ tokio = {version = "1.19.1", features = ["full"] }
|
||||
|
||||
mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" }
|
||||
network-defaults = { path = "../common/network-defaults" }
|
||||
task = { path = "../common/task" }
|
||||
validator-client = { path = "../common/client-libs/validator-client", features=["nymd-client"] }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use log::info;
|
||||
use task::ShutdownListener;
|
||||
|
||||
use crate::country_statistics::country_nodes_distribution::CountryNodesDistribution;
|
||||
use crate::COUNTRY_DATA_REFRESH_INTERVAL;
|
||||
@@ -7,11 +8,12 @@ use crate::state::ExplorerApiStateContext;
|
||||
|
||||
pub(crate) struct CountryStatisticsDistributionTask {
|
||||
state: ExplorerApiStateContext,
|
||||
shutdown: ShutdownListener,
|
||||
}
|
||||
|
||||
impl CountryStatisticsDistributionTask {
|
||||
pub(crate) fn new(state: ExplorerApiStateContext) -> Self {
|
||||
CountryStatisticsDistributionTask { state }
|
||||
pub(crate) fn new(state: ExplorerApiStateContext, shutdown: ShutdownListener) -> Self {
|
||||
CountryStatisticsDistributionTask { state, shutdown }
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self) {
|
||||
@@ -20,10 +22,15 @@ impl CountryStatisticsDistributionTask {
|
||||
let mut interval_timer = tokio::time::interval(std::time::Duration::from_secs(
|
||||
COUNTRY_DATA_REFRESH_INTERVAL,
|
||||
));
|
||||
loop {
|
||||
// wait for the next interval tick
|
||||
interval_timer.tick().await;
|
||||
self.calculate_nodes_per_country().await;
|
||||
while !self.shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
_ = interval_timer.tick() => {
|
||||
self.calculate_nodes_per_country().await;
|
||||
}
|
||||
_ = self.shutdown.recv() => {
|
||||
trace!("Listener: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,15 +5,17 @@ use crate::mix_nodes::location::{GeoLocation, Location};
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
use log::{info, warn};
|
||||
use reqwest::Error as ReqwestError;
|
||||
use task::ShutdownListener;
|
||||
use thiserror::Error;
|
||||
|
||||
pub(crate) struct GeoLocateTask {
|
||||
state: ExplorerApiStateContext,
|
||||
shutdown: ShutdownListener,
|
||||
}
|
||||
|
||||
impl GeoLocateTask {
|
||||
pub(crate) fn new(state: ExplorerApiStateContext) -> Self {
|
||||
GeoLocateTask { state }
|
||||
pub(crate) fn new(state: ExplorerApiStateContext, shutdown: ShutdownListener) -> Self {
|
||||
GeoLocateTask { state, shutdown }
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self) {
|
||||
@@ -27,10 +29,15 @@ impl GeoLocateTask {
|
||||
info!("Spawning mix node locator task runner...");
|
||||
tokio::spawn(async move {
|
||||
let mut interval_timer = tokio::time::interval(std::time::Duration::from_millis(50));
|
||||
loop {
|
||||
// wait for the next interval tick
|
||||
interval_timer.tick().await;
|
||||
self.locate_mix_nodes().await;
|
||||
while !self.shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
_ = interval_timer.tick() => {
|
||||
self.locate_mix_nodes().await;
|
||||
}
|
||||
_ = self.shutdown.recv() => {
|
||||
trace!("Listener: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+47
-12
@@ -6,6 +6,7 @@ extern crate rocket_okapi;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use network_defaults::setup_env;
|
||||
use task::ShutdownNotifier;
|
||||
|
||||
pub(crate) mod cache;
|
||||
mod client;
|
||||
@@ -50,29 +51,63 @@ impl ExplorerApi {
|
||||
let validator_api_url = self.state.inner.validator_client.api_endpoint();
|
||||
info!("Using validator API - {}", validator_api_url);
|
||||
|
||||
let shutdown = ShutdownNotifier::default();
|
||||
|
||||
// spawn concurrent tasks
|
||||
crate::tasks::ExplorerApiTasks::new(self.state.clone()).start();
|
||||
crate::tasks::ExplorerApiTasks::new(self.state.clone(), shutdown.subscribe()).start();
|
||||
country_statistics::distribution::CountryStatisticsDistributionTask::new(
|
||||
self.state.clone(),
|
||||
shutdown.subscribe(),
|
||||
)
|
||||
.start();
|
||||
country_statistics::geolocate::GeoLocateTask::new(self.state.clone()).start();
|
||||
country_statistics::geolocate::GeoLocateTask::new(self.state.clone(), shutdown.subscribe())
|
||||
.start();
|
||||
|
||||
// Rocket handles shutdown on it's own, but its shutdown handling should be incorporated
|
||||
// with that of the rest of the tasks.
|
||||
// Currently it's runtime is forcefully terminated once the explorer-api exits.
|
||||
http::start(self.state.clone());
|
||||
|
||||
// wait for user to press ctrl+C
|
||||
self.wait_for_interrupt().await
|
||||
self.wait_for_interrupt(shutdown).await
|
||||
}
|
||||
|
||||
async fn wait_for_interrupt(&self) {
|
||||
if let Err(e) = tokio::signal::ctrl_c().await {
|
||||
error!(
|
||||
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
|
||||
e
|
||||
);
|
||||
async fn wait_for_interrupt(&self, mut shutdown: ShutdownNotifier) {
|
||||
wait_for_signal().await;
|
||||
|
||||
log::info!("Sending shutdown");
|
||||
shutdown.signal_shutdown().ok();
|
||||
log::info!("Waiting for tasks to finish... (Press ctrl-c to force)");
|
||||
shutdown.wait_for_shutdown().await;
|
||||
log::info!("Stopping explorer API");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn wait_for_signal() {
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
let mut sigterm = signal(SignalKind::terminate()).expect("Failed to setup SIGTERM channel");
|
||||
let mut sigquit = signal(SignalKind::quit()).expect("Failed to setup SIGQUIT channel");
|
||||
|
||||
tokio::select! {
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
log::info!("Received SIGINT");
|
||||
},
|
||||
_ = sigterm.recv() => {
|
||||
log::info!("Received SIGTERM");
|
||||
}
|
||||
info!(
|
||||
"Received SIGINT - the mixnode will terminate now (threads are not yet nicely stopped, if you see stack traces that's alright)."
|
||||
);
|
||||
_ = sigquit.recv() => {
|
||||
log::info!("Received SIGQUIT");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
async fn wait_for_signal() {
|
||||
tokio::select! {
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
log::info!("Received SIGINT");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+21
-15
@@ -4,6 +4,7 @@
|
||||
use std::future::Future;
|
||||
|
||||
use mixnet_contract_common::GatewayBond;
|
||||
use task::ShutdownListener;
|
||||
use validator_client::models::MixNodeBondAnnotated;
|
||||
use validator_client::nymd::error::NymdError;
|
||||
use validator_client::nymd::{Paging, QueryNymdClient, ValidatorResponse};
|
||||
@@ -14,11 +15,12 @@ use crate::state::ExplorerApiStateContext;
|
||||
|
||||
pub(crate) struct ExplorerApiTasks {
|
||||
state: ExplorerApiStateContext,
|
||||
shutdown: ShutdownListener,
|
||||
}
|
||||
|
||||
impl ExplorerApiTasks {
|
||||
pub(crate) fn new(state: ExplorerApiStateContext) -> Self {
|
||||
ExplorerApiTasks { state }
|
||||
pub(crate) fn new(state: ExplorerApiStateContext, shutdown: ShutdownListener) -> Self {
|
||||
ExplorerApiTasks { state, shutdown }
|
||||
}
|
||||
|
||||
// a helper to remove duplicate code when grabbing active/rewarded/all mixnodes
|
||||
@@ -128,24 +130,28 @@ impl ExplorerApiTasks {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start(self) {
|
||||
pub(crate) fn start(mut self) {
|
||||
info!("Spawning mix nodes task runner...");
|
||||
tokio::spawn(async move {
|
||||
let mut interval_timer = tokio::time::interval(CACHE_REFRESH_RATE);
|
||||
loop {
|
||||
// wait for the next interval tick
|
||||
interval_timer.tick().await;
|
||||
while !self.shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
_ = interval_timer.tick() => {
|
||||
info!("Updating validator cache...");
|
||||
self.update_validators_cache().await;
|
||||
info!("Done");
|
||||
|
||||
info!("Updating validator cache...");
|
||||
self.update_validators_cache().await;
|
||||
info!("Done");
|
||||
info!("Updating gateway cache...");
|
||||
self.update_gateways_cache().await;
|
||||
info!("Done");
|
||||
|
||||
info!("Updating gateway cache...");
|
||||
self.update_gateways_cache().await;
|
||||
info!("Done");
|
||||
|
||||
info!("Updating mix node cache...");
|
||||
self.update_mixnode_cache().await;
|
||||
info!("Updating mix node cache...");
|
||||
self.update_mixnode_cache().await;
|
||||
}
|
||||
_ = self.shutdown.recv() => {
|
||||
trace!("Listener: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
EXPLORER_API_URL=https://sandbox-explorer.nymtech.net/api/v1
|
||||
VALIDATOR_API_URL=https://sandbox-validator.nymtech.net
|
||||
BIG_DIPPER_URL=https://sandbox-blocks.nymtech.net
|
||||
CURRENCY_DENOM=unymt
|
||||
CURRENCY_STAKING_DENOM=unyxt
|
||||
EXPLORER_API_URL=https://explorer.nymtech.net/api/v1
|
||||
VALIDATOR_API_URL=https://validator.nymtech.net
|
||||
BIG_DIPPER_URL=https://blocks.nymtech.net
|
||||
CURRENCY_DENOM=unym
|
||||
CURRENCY_STAKING_DENOM=unyx
|
||||
|
||||
@@ -336,6 +336,7 @@ where
|
||||
if self.config.get_enabled_statistics() {
|
||||
let statistics_service_url = self.config.get_statistics_service_url();
|
||||
let stats_collector = GatewayStatisticsCollector::new(
|
||||
self.identity_keypair.public_key().to_base58_string(),
|
||||
active_clients_store.clone(),
|
||||
statistics_service_url,
|
||||
);
|
||||
|
||||
@@ -14,13 +14,19 @@ use statistics_common::{
|
||||
use crate::node::client_handling::active_clients::ActiveClientsStore;
|
||||
|
||||
pub(crate) struct GatewayStatisticsCollector {
|
||||
gateway_id: String,
|
||||
active_clients_store: ActiveClientsStore,
|
||||
statistics_service_url: Url,
|
||||
}
|
||||
|
||||
impl GatewayStatisticsCollector {
|
||||
pub fn new(active_clients_store: ActiveClientsStore, statistics_service_url: Url) -> Self {
|
||||
pub fn new(
|
||||
gateway_id: String,
|
||||
active_clients_store: ActiveClientsStore,
|
||||
statistics_service_url: Url,
|
||||
) -> Self {
|
||||
GatewayStatisticsCollector {
|
||||
gateway_id,
|
||||
active_clients_store,
|
||||
statistics_service_url,
|
||||
}
|
||||
@@ -35,7 +41,10 @@ impl StatisticsCollector for GatewayStatisticsCollector {
|
||||
timestamp: DateTime<Utc>,
|
||||
) -> StatsMessage {
|
||||
let inbox_count = self.active_clients_store.size() as u32;
|
||||
let stats_data = vec![StatsData::Gateway(StatsGatewayData { inbox_count })];
|
||||
let stats_data = vec![StatsData::Gateway(StatsGatewayData::new(
|
||||
self.gateway_id.clone(),
|
||||
inbox_count,
|
||||
))];
|
||||
StatsMessage {
|
||||
stats_data,
|
||||
interval_seconds: interval.as_secs() as u32,
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ fn long_version_static() -> &'static str {
|
||||
#[derive(Parser)]
|
||||
#[clap(author = "Nymtech", version, about, long_version = long_version_static())]
|
||||
struct Cli {
|
||||
/// Path pointing to an env file that configures the gateway.
|
||||
/// Path pointing to an env file that configures the mixnode.
|
||||
#[clap(long)]
|
||||
pub(crate) config_env_file: Option<std::path::PathBuf>,
|
||||
|
||||
|
||||
Generated
+1983
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "chitchat-test"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
chitchat = "0.4"
|
||||
poem = "1"
|
||||
poem-openapi = {version="2", features = ["swagger-ui"] }
|
||||
structopt = "0.3"
|
||||
tokio = { version = "1.14.0", features = ["net", "sync", "rt-multi-thread", "macros", "time"] }
|
||||
serde = { version="1", features=["derive"] }
|
||||
serde_json = "1"
|
||||
anyhow = "1"
|
||||
once_cell = "1"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
cool-id-generator = "1"
|
||||
env_logger = "0.9"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2"
|
||||
predicates = "2"
|
||||
reqwest = { version = "0.11", default-features=false, features = ["blocking", "json"] }
|
||||
|
||||
[workspace]
|
||||
@@ -0,0 +1,15 @@
|
||||
# Chitchat test
|
||||
|
||||
Runs simple chitchat servers, mostly copied over from https://github.com/quickwit-oss/chitchat
|
||||
|
||||
## Example
|
||||
|
||||
```bash
|
||||
# Starts 5 servers and joins them into a cluster on localhost ports 10000-10004
|
||||
# All servers print cluster state on `/` ie 127.0.0.1:10000
|
||||
# `/docs` endpoint has an open api with a key value setter, set it on one node and observe how the state propagates to the other nodes
|
||||
# NodeState is a regular BTreeMap
|
||||
./run-servers.sh
|
||||
|
||||
# run killall chitchat-test after you're done, as the servers will continue to run forever in the background
|
||||
```
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
killall chitchat-test
|
||||
|
||||
cargo build --release
|
||||
|
||||
for i in $(seq 10000 10004)
|
||||
do
|
||||
listen_addr="127.0.0.1:$i";
|
||||
echo ${listen_addr};
|
||||
cargo run --release -- --listen_addr ${listen_addr} --seed 127.0.0.1:10000 --node_id node_$i &
|
||||
done;
|
||||
|
||||
read
|
||||
kill 0
|
||||
@@ -0,0 +1,123 @@
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use chitchat::transport::UdpTransport;
|
||||
use chitchat::{spawn_chitchat, Chitchat, ChitchatConfig, FailureDetectorConfig, NodeId};
|
||||
use cool_id_generator::Size;
|
||||
use poem::listener::TcpListener;
|
||||
use poem::{Route, Server};
|
||||
use poem_openapi::param::Query;
|
||||
use poem_openapi::payload::Json;
|
||||
use poem_openapi::{OpenApi, OpenApiService};
|
||||
use structopt::StructOpt;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use chitchat::ClusterStateSnapshot;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ApiResponse {
|
||||
pub cluster_id: String,
|
||||
pub cluster_state: ClusterStateSnapshot,
|
||||
pub live_nodes: Vec<NodeId>,
|
||||
pub dead_nodes: Vec<NodeId>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SetKeyValueResponse {
|
||||
pub status: bool,
|
||||
}
|
||||
|
||||
struct Api {
|
||||
chitchat: Arc<Mutex<Chitchat>>,
|
||||
}
|
||||
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
/// Chitchat state
|
||||
#[oai(path = "/", method = "get")]
|
||||
async fn index(&self) -> Json<serde_json::Value> {
|
||||
let chitchat_guard = self.chitchat.lock().await;
|
||||
let response = ApiResponse {
|
||||
cluster_id: chitchat_guard.cluster_id().to_string(),
|
||||
cluster_state: chitchat_guard.state_snapshot(),
|
||||
live_nodes: chitchat_guard.live_nodes().cloned().collect::<Vec<_>>(),
|
||||
dead_nodes: chitchat_guard.dead_nodes().cloned().collect::<Vec<_>>(),
|
||||
};
|
||||
Json(serde_json::to_value(&response).unwrap())
|
||||
}
|
||||
|
||||
/// Set a key & value on this node (with no validation).
|
||||
#[oai(path = "/set_kv/", method = "get")]
|
||||
async fn set_kv(&self, key: Query<String>, value: Query<String>) -> Json<serde_json::Value> {
|
||||
let mut chitchat_guard = self.chitchat.lock().await;
|
||||
|
||||
let cc_state = chitchat_guard.self_node_state();
|
||||
cc_state.set(key.as_str(), value.as_str());
|
||||
|
||||
Json(serde_json::to_value(&SetKeyValueResponse { status: true }).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[structopt(name = "chitchat", about = "Chitchat test server.")]
|
||||
struct Opt {
|
||||
/// Defines the socket addr on which we should listen to.
|
||||
#[structopt(long = "listen_addr", default_value = "127.0.0.1:10000")]
|
||||
listen_addr: SocketAddr,
|
||||
/// Defines the socket_address (host:port) other servers should use to
|
||||
/// reach this server.
|
||||
///
|
||||
/// It defaults to the listen address, but this is only valid
|
||||
/// when all server are running on the same server.
|
||||
#[structopt(long = "public_addr")]
|
||||
public_addr: Option<SocketAddr>,
|
||||
|
||||
/// Node id. Has to be unique. If None, the node_id will be generated from
|
||||
/// the public_addr and a random suffix.
|
||||
#[structopt(long = "node_id")]
|
||||
node_id: Option<String>,
|
||||
|
||||
#[structopt(long = "seed")]
|
||||
seeds: Vec<String>,
|
||||
|
||||
#[structopt(long = "interval_ms", default_value = "500")]
|
||||
interval: u64,
|
||||
}
|
||||
|
||||
fn generate_server_id(public_addr: SocketAddr) -> String {
|
||||
let cool_id = cool_id_generator::get_id(Size::Medium);
|
||||
format!("server:{}-{}", public_addr, cool_id)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
let opt = Opt::from_args();
|
||||
println!("{:?}", opt);
|
||||
let public_addr = opt.public_addr.unwrap_or(opt.listen_addr);
|
||||
let node_id_str = opt
|
||||
.node_id
|
||||
.unwrap_or_else(|| generate_server_id(public_addr));
|
||||
let node_id = NodeId::new(node_id_str, public_addr);
|
||||
let config = ChitchatConfig {
|
||||
node_id,
|
||||
cluster_id: "testing".to_string(),
|
||||
gossip_interval: Duration::from_millis(opt.interval),
|
||||
listen_addr: opt.listen_addr,
|
||||
seed_nodes: opt.seeds.clone(),
|
||||
failure_detector_config: FailureDetectorConfig::default(),
|
||||
};
|
||||
let chitchat_handler = spawn_chitchat(config, Vec::new(), &UdpTransport).await?;
|
||||
let chitchat = chitchat_handler.chitchat();
|
||||
let api = Api { chitchat };
|
||||
let api_service = OpenApiService::new(api, "Hello World", "1.0")
|
||||
.server(&format!("http://{}/", opt.listen_addr));
|
||||
let docs = api_service.swagger_ui();
|
||||
let app = Route::new().nest("/", api_service).nest("/docs", docs);
|
||||
Server::new(TcpListener::bind(&opt.listen_addr))
|
||||
.run(app)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
Generated
+1
-2
@@ -3199,7 +3199,6 @@ dependencies = [
|
||||
"cosmwasm-std",
|
||||
"fixed",
|
||||
"log",
|
||||
"network-defaults",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
@@ -3405,7 +3404,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-connect"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"bip39",
|
||||
"client-core",
|
||||
|
||||
@@ -32,7 +32,7 @@ yarn install
|
||||
|
||||
## Development mode
|
||||
|
||||
You can compile nym-connectin development mode by running the following command inside the `nym-connect` directory:
|
||||
You can compile nym-connect in development mode by running the following command inside the `nym-connect` directory:
|
||||
|
||||
```
|
||||
yarn dev
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-connect"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
description = "nym-connect"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
@@ -39,7 +39,7 @@ tokio = { version = "1.19.1", features = ["sync", "time"] }
|
||||
url = "2.2"
|
||||
|
||||
client-core = { path = "../../clients/client-core" }
|
||||
config = { path = "../../common/config" }
|
||||
config-common = { path = "../../common/config", package = "config" }
|
||||
nym-socks5-client = { path = "../../clients/socks5" }
|
||||
topology = { path = "../../common/topology" }
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ use tap::TapFallible;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use client_core::config::Config as BaseConfig;
|
||||
use config::NymConfig;
|
||||
use nym_socks5::client::config::Config as Socks5Config;
|
||||
use config_common::NymConfig;
|
||||
use nym_socks5::{client::config::Config as Socks5Config, commands::parse_validators};
|
||||
|
||||
use crate::{
|
||||
error::{BackendError, Result},
|
||||
@@ -134,6 +134,11 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str
|
||||
config
|
||||
.get_base_mut()
|
||||
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
|
||||
if let Ok(raw_validators) = std::env::var(config_common::defaults::var_names::API_VALIDATOR) {
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_validator_apis(parse_validators(&raw_validators));
|
||||
}
|
||||
|
||||
let gateway = setup_gateway(
|
||||
&id,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use config_common::defaults::setup_env;
|
||||
use tauri::Menu;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
@@ -24,6 +25,7 @@ mod window;
|
||||
|
||||
fn main() {
|
||||
setup_logging();
|
||||
setup_env(None);
|
||||
println!("Starting up...");
|
||||
|
||||
// As per breaking change description here
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use ::config::NymConfig;
|
||||
use ::config_common::NymConfig;
|
||||
use futures::SinkExt;
|
||||
use tap::TapFallible;
|
||||
use tauri::Manager;
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::sync::Arc;
|
||||
use tap::TapFallible;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use config::NymConfig;
|
||||
use config_common::NymConfig;
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use nym_socks5::client::NymClient as Socks5NymClient;
|
||||
use nym_socks5::client::{config::Config as Socks5Config, Socks5ControlMessageSender};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-connect",
|
||||
"version": "1.0.0"
|
||||
"version": "1.0.1"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
|
||||
@@ -22,11 +22,14 @@ export const DefaultLayout: React.FC<{
|
||||
};
|
||||
return (
|
||||
<AppWindowFrame>
|
||||
<Typography fontWeight="700" fontSize="14px" textAlign="center">
|
||||
Connect, your privacy will be 100% protected thanks to the Nym Mixnet
|
||||
<Typography fontWeight="400" fontSize="12px" textAlign="center" sx={{ opacity: 0.6 }}>
|
||||
This is experimental software. <br />
|
||||
Do not rely on it for strong anonymity (yet).
|
||||
</Typography>
|
||||
<Typography fontWeight="700" fontSize="14px" textAlign="center" color="#60D6EF" pt={2}>
|
||||
You are not protected now
|
||||
<Typography fontWeight="700" fontSize="14px" textAlign="center" pt={2}>
|
||||
Connect to the
|
||||
<br />
|
||||
Nym mixnet for privacy.
|
||||
</Typography>
|
||||
<ServiceProviderSelector services={services} onChange={handleServiceProviderChange} />
|
||||
<ConnectionButton
|
||||
|
||||
Generated
+4
-4
@@ -3996,9 +3996,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.4"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -4016,9 +4016,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.25"
|
||||
version = "0.6.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { Box, Typography, SxProps } from '@mui/material';
|
||||
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||
import { CurrencyDenom, FeeDetails, DecCoin } from '@nymproject/types';
|
||||
@@ -31,7 +30,7 @@ export const DelegateModal: React.FC<{
|
||||
estimatedReward?: number;
|
||||
profitMarginPercentage?: number | null;
|
||||
nodeUptimePercentage?: number | null;
|
||||
currency: string;
|
||||
denom: CurrencyDenom;
|
||||
initialAmount?: string;
|
||||
hasVestingContract: boolean;
|
||||
sx?: SxProps;
|
||||
@@ -48,7 +47,7 @@ export const DelegateModal: React.FC<{
|
||||
rewardInterval,
|
||||
accountBalance,
|
||||
estimatedReward,
|
||||
currency,
|
||||
denom,
|
||||
profitMarginPercentage,
|
||||
nodeUptimePercentage,
|
||||
initialAmount,
|
||||
@@ -103,7 +102,7 @@ export const DelegateModal: React.FC<{
|
||||
}
|
||||
|
||||
if (amount && Number(amount) < MIN_AMOUNT_TO_DELEGATE) {
|
||||
errorAmountMessage = `Min. delegation amount: ${MIN_AMOUNT_TO_DELEGATE} ${currency}`;
|
||||
errorAmountMessage = `Min. delegation amount: ${MIN_AMOUNT_TO_DELEGATE} ${denom.toUpperCase()}`;
|
||||
newValidatedValue = false;
|
||||
}
|
||||
|
||||
@@ -118,7 +117,7 @@ export const DelegateModal: React.FC<{
|
||||
|
||||
const handleOk = async () => {
|
||||
if (onOk && amount && identityKey) {
|
||||
onOk(identityKey, { amount, denom: currency as CurrencyDenom }, tokenPool, fee);
|
||||
onOk(identityKey, { amount, denom }, tokenPool, fee);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -170,7 +169,7 @@ export const DelegateModal: React.FC<{
|
||||
onConfirm={handleOk}
|
||||
>
|
||||
<ModalListItem label="Node identity key" value={identityKey} divider />
|
||||
<ModalListItem label="Amount" value={`${amount} ${currency}`} divider />
|
||||
<ModalListItem label="Amount" value={`${amount} ${denom.toUpperCase()}`} divider />
|
||||
</ConfirmTx>
|
||||
);
|
||||
}
|
||||
@@ -181,7 +180,7 @@ export const DelegateModal: React.FC<{
|
||||
onClose={onClose}
|
||||
onOk={async () => {
|
||||
if (identityKey && amount) {
|
||||
handleConfirm({ identity: identityKey, value: { amount, denom: currency as CurrencyDenom } });
|
||||
handleConfirm({ identity: identityKey, value: { amount, denom } });
|
||||
}
|
||||
}}
|
||||
header={header || 'Delegate'}
|
||||
@@ -219,6 +218,7 @@ export const DelegateModal: React.FC<{
|
||||
initialValue={amount}
|
||||
autoFocus={Boolean(initialIdentityKey)}
|
||||
onChanged={handleAmountChanged}
|
||||
denom={denom}
|
||||
/>
|
||||
</Box>
|
||||
<Typography
|
||||
@@ -247,7 +247,12 @@ export const DelegateModal: React.FC<{
|
||||
divider
|
||||
/>
|
||||
|
||||
<ModalListItem label="Node est. reward per epoch" value={`${estimatedReward} ${currency}`} hidden divider />
|
||||
<ModalListItem
|
||||
label="Node est. reward per epoch"
|
||||
value={`${estimatedReward} ${denom.toUpperCase()}`}
|
||||
hidden
|
||||
divider
|
||||
/>
|
||||
</SimpleModal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -144,7 +144,7 @@ export const DelegationsActionsMenu: React.FC<{
|
||||
/>
|
||||
<DelegationActionsMenuItem
|
||||
title="Redeem"
|
||||
description="Trasfer your rewards to your balance"
|
||||
description="Transfer your rewards to your balance"
|
||||
Icon={<Typography sx={{ pl: 1 }}>R</Typography>}
|
||||
onClick={() => handleActionSelect?.('redeem')}
|
||||
disabled={disableRedeemingRewards}
|
||||
|
||||
@@ -61,7 +61,7 @@ export const Delegate = () => {
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
currency="nym"
|
||||
denom="nym"
|
||||
estimatedReward={50.423}
|
||||
accountBalance="425.2345053"
|
||||
nodeUptimePercentage={99.28394}
|
||||
@@ -84,7 +84,7 @@ export const DelegateBelowMinimum = () => {
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
currency="nym"
|
||||
denom="nym"
|
||||
estimatedReward={425.2345053}
|
||||
nodeUptimePercentage={99.28394}
|
||||
profitMarginPercentage={11.12334234}
|
||||
@@ -109,7 +109,7 @@ export const DelegateMore = () => {
|
||||
onOk={async () => setOpen(false)}
|
||||
header="Delegate more"
|
||||
buttonText="Delegate more"
|
||||
currency="nym"
|
||||
denom="nym"
|
||||
estimatedReward={50.423}
|
||||
accountBalance="425.2345053"
|
||||
nodeUptimePercentage={99.28394}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Box, CircularProgress, Modal, Stack, Typography } from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { Box, CircularProgress, Modal, Stack, Typography, SxProps } from '@mui/material';
|
||||
|
||||
const modalStyle: SxProps = {
|
||||
position: 'absolute',
|
||||
|
||||
@@ -60,12 +60,16 @@ export const SimpleModal: React.FC<{
|
||||
|
||||
{children}
|
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mt: 2 }}>
|
||||
{onBack && <StyledBackButton onBack={onBack} />}
|
||||
<Button variant="contained" fullWidth size="large" onClick={onOk} disabled={okDisabled}>
|
||||
{okLabel}
|
||||
</Button>
|
||||
</Box>
|
||||
{(onOk || onBack) && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mt: 2 }}>
|
||||
{onBack && <StyledBackButton onBack={onBack} />}
|
||||
{onOk && (
|
||||
<Button variant="contained" fullWidth size="large" onClick={onOk} disabled={okDisabled}>
|
||||
{okLabel}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -2,14 +2,14 @@ import React, { useState, useContext } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { List, ListItem, ListItemIcon, ListItemText } from '@mui/material';
|
||||
import { AccountBalanceWalletOutlined, ArrowBack, ArrowForward, Description, Settings } from '@mui/icons-material';
|
||||
import { AppContext } from '../context/main';
|
||||
import { AppContext } from '../context';
|
||||
import { Bond, Delegate, Unbond } from '../svg-icons';
|
||||
|
||||
export const Nav = () => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { isAdminAddress, handleShowSendModal } = useContext(AppContext);
|
||||
const { isAdminAddress, handleShowSendModal, handleShowReceiveModal } = useContext(AppContext);
|
||||
|
||||
const [routesSchema] = useState([
|
||||
{
|
||||
@@ -25,9 +25,8 @@ export const Nav = () => {
|
||||
},
|
||||
{
|
||||
label: 'Receive',
|
||||
route: '/receive',
|
||||
Icon: ArrowBack,
|
||||
onClick: () => navigate('/receive'),
|
||||
onClick: handleShowReceiveModal,
|
||||
},
|
||||
{
|
||||
label: 'Bond',
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { AppContext } from 'src/context';
|
||||
import { Box, Stack, Typography, SxProps } from '@mui/material';
|
||||
import QRCode from 'qrcode.react';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
import { ClientAddress } from '../ClientAddress';
|
||||
|
||||
export const ReceiveModal = ({
|
||||
onClose,
|
||||
open,
|
||||
sx,
|
||||
backdropProps,
|
||||
}: {
|
||||
onClose: () => void;
|
||||
open: boolean;
|
||||
sx?: SxProps;
|
||||
backdropProps?: object;
|
||||
}) => {
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
return (
|
||||
<SimpleModal
|
||||
header="Receive"
|
||||
okLabel="Ok"
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
sx={{ width: 'small', ...sx }}
|
||||
backdropProps={backdropProps}
|
||||
>
|
||||
<Stack spacing={3} sx={{ mt: 1.6 }}>
|
||||
<Stack direction="row" alignItems="center" gap={4}>
|
||||
<Typography>Your address:</Typography>
|
||||
<ClientAddress withCopy showEntireAddress />
|
||||
</Stack>
|
||||
<Stack alignItems="center">
|
||||
<Box sx={{ border: (t) => `1px solid ${t.palette.nym.highlight}`, borderRadius: 2, p: 2 }}>
|
||||
{clientDetails && <QRCode data-testid="qr-code" value={clientDetails?.client_address} />}
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</SimpleModal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { AppContext } from 'src/context';
|
||||
import { ReceiveModal } from './ReceiveModal';
|
||||
|
||||
export const Receive = ({ hasStorybookStyles }: { hasStorybookStyles?: {} }) => {
|
||||
const { showReceiveModal, handleShowReceiveModal } = useContext(AppContext);
|
||||
|
||||
if (showReceiveModal)
|
||||
return <ReceiveModal onClose={handleShowReceiveModal} open={showReceiveModal} {...hasStorybookStyles} />;
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Stack, Typography } from '@mui/material';
|
||||
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { CurrencyDenom, FeeDetails } from '@nymproject/types';
|
||||
import { simulateCompoundDelgatorReward, simulateVestingCompoundDelgatorReward } from 'src/requests';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
@@ -14,10 +14,10 @@ export const CompoundModal: React.FC<{
|
||||
onOk?: (identityKey: string, fee?: FeeDetails) => void;
|
||||
identityKey: string;
|
||||
amount: number;
|
||||
currency: string;
|
||||
denom: CurrencyDenom;
|
||||
message: string;
|
||||
usesVestingTokens: boolean;
|
||||
}> = ({ open, onClose, onOk, identityKey, amount, currency, message, usesVestingTokens }) => {
|
||||
}> = ({ open, onClose, onOk, identityKey, amount, denom, message, usesVestingTokens }) => {
|
||||
const { fee, isFeeLoading, feeError, getFee } = useGetFee();
|
||||
|
||||
const handleOk = async () => {
|
||||
@@ -47,7 +47,7 @@ export const CompoundModal: React.FC<{
|
||||
<Stack direction="row" justifyContent="space-between" mb={4} mt={identityKey && 4}>
|
||||
<Typography>Rewards amount:</Typography>
|
||||
<Typography>
|
||||
{amount} {currency}
|
||||
{amount} {denom.toUpperCase()}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export const RedeemAllRewards = () => {
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
message="Redeem all rewards"
|
||||
currency="nym"
|
||||
denom="nym"
|
||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
||||
amount={425.65843}
|
||||
{...storybookStyles(theme)}
|
||||
@@ -82,7 +82,7 @@ export const RedeemRewardForMixnode = () => {
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
message="Redeem rewards"
|
||||
currency="nym"
|
||||
denom="nym"
|
||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
||||
amount={425.65843}
|
||||
{...storybookStyles(theme)}
|
||||
@@ -103,7 +103,7 @@ export const FeeIsMoreThanAllRewards = () => {
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={() => setOpen(false)}
|
||||
message="Redeem all rewards"
|
||||
currency="nym"
|
||||
denom="nym"
|
||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
||||
amount={0.001}
|
||||
{...storybookStyles(theme)}
|
||||
@@ -125,7 +125,7 @@ export const FeeIsMoreThanMixnodeReward = () => {
|
||||
onOk={async () => setOpen(false)}
|
||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
||||
message="Redeem rewards"
|
||||
currency="nym"
|
||||
denom="nym"
|
||||
amount={0.001}
|
||||
{...storybookStyles(theme)}
|
||||
usesVestingTokens={false}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Stack, Typography, SxProps } from '@mui/material';
|
||||
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { CurrencyDenom, FeeDetails } from '@nymproject/types';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { simulateClaimDelgatorReward, simulateVestingClaimDelgatorReward } from 'src/requests';
|
||||
import { ModalFee } from '../Modals/ModalFee';
|
||||
@@ -14,12 +14,12 @@ export const RedeemModal: React.FC<{
|
||||
onOk?: (identityKey: string, fee?: FeeDetails) => void;
|
||||
identityKey: string;
|
||||
amount: number;
|
||||
currency: string;
|
||||
denom: CurrencyDenom;
|
||||
message: string;
|
||||
sx?: SxProps;
|
||||
backdropProps?: Object;
|
||||
usesVestingTokens: boolean;
|
||||
}> = ({ open, onClose, onOk, identityKey, amount, currency, message, usesVestingTokens, sx, backdropProps }) => {
|
||||
}> = ({ open, onClose, onOk, identityKey, amount, denom, message, usesVestingTokens, sx, backdropProps }) => {
|
||||
const { fee, isFeeLoading, feeError, getFee } = useGetFee();
|
||||
|
||||
const handleOk = async () => {
|
||||
@@ -52,7 +52,7 @@ export const RedeemModal: React.FC<{
|
||||
<Stack direction="row" justifyContent="space-between" mb={4} mt={identityKey && 4}>
|
||||
<Typography sx={{ color: 'text.primary' }}>Rewards amount:</Typography>
|
||||
<Typography sx={{ color: 'text.primary' }}>
|
||||
{amount} {currency}
|
||||
{amount} {denom.toUpperCase()}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ export const SendDetails = () => {
|
||||
fromAddress="nymt1w8qp7zsxggvtxhpqpt6e329j42wtv07dm5ts8u"
|
||||
toAddress="nymt1w8qp7zsxggvtxhpqpt6e329j42wtv07dm5ts8u"
|
||||
fee={{ amount: { amount: '0.01', denom: 'nym' }, fee: { Auto: null } }}
|
||||
denom="nym"
|
||||
amount={{ amount: '100', denom: 'nym' }}
|
||||
onPrev={() => {}}
|
||||
onSend={() => {}}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Stack } from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { Stack, SxProps } from '@mui/material';
|
||||
import { CurrencyDenom } from '@nymproject/types';
|
||||
import { FeeDetails, DecCoin } from '@nymproject/types';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
import { ModalListItem } from '../Modals/ModalListItem';
|
||||
@@ -10,6 +10,7 @@ export const SendDetailsModal = ({
|
||||
toAddress,
|
||||
fromAddress,
|
||||
fee,
|
||||
denom,
|
||||
onClose,
|
||||
onPrev,
|
||||
onSend,
|
||||
@@ -20,6 +21,7 @@ export const SendDetailsModal = ({
|
||||
toAddress: string;
|
||||
fee?: FeeDetails;
|
||||
amount?: DecCoin;
|
||||
denom: CurrencyDenom;
|
||||
onClose: () => void;
|
||||
onPrev: () => void;
|
||||
onSend: (data: { val: DecCoin; to: string }) => void;
|
||||
@@ -39,7 +41,7 @@ export const SendDetailsModal = ({
|
||||
<Stack gap={0.5} sx={{ mt: 4 }}>
|
||||
<ModalListItem label="From" value={fromAddress} divider />
|
||||
<ModalListItem label="To" value={toAddress} divider />
|
||||
<ModalListItem label="Amount" value={`${amount?.amount} ${amount?.denom}`} divider />
|
||||
<ModalListItem label="Amount" value={`${amount?.amount} ${denom.toUpperCase()}`} divider />
|
||||
<ModalListItem
|
||||
label="Fee for this transaction"
|
||||
value={!fee ? 'n/a' : `${fee.amount?.amount} ${fee.amount?.denom}`}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { SxProps } from '@mui/material';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
|
||||
export const SendErrorModal = ({
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Stack, TextField, Typography } from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { Stack, TextField, Typography, SxProps } from '@mui/material';
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||
import { DecCoin } from '@nymproject/types';
|
||||
import { CurrencyDenom, DecCoin } from '@nymproject/types';
|
||||
import { validateAmount } from 'src/utils';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
import { ModalListItem } from '../Modals/ModalListItem';
|
||||
@@ -12,6 +11,7 @@ export const SendInputModal = ({
|
||||
toAddress,
|
||||
amount,
|
||||
balance,
|
||||
denom,
|
||||
error,
|
||||
onNext,
|
||||
onClose,
|
||||
@@ -24,6 +24,7 @@ export const SendInputModal = ({
|
||||
toAddress: string;
|
||||
amount?: DecCoin;
|
||||
balance?: string;
|
||||
denom?: CurrencyDenom;
|
||||
error?: string;
|
||||
onNext: () => void;
|
||||
onClose: () => void;
|
||||
@@ -69,6 +70,7 @@ export const SendInputModal = ({
|
||||
validate(value);
|
||||
}}
|
||||
initialValue={amount?.amount}
|
||||
denom={denom}
|
||||
/>
|
||||
<Typography fontSize="smaller" sx={{ color: 'error.main' }}>
|
||||
{error}
|
||||
|
||||
@@ -21,7 +21,7 @@ export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [txDetails, setTxDetails] = useState<TTransactionDetails>();
|
||||
|
||||
const { clientDetails, userBalance, network, denom } = useContext(AppContext);
|
||||
const { clientDetails, userBalance, network } = useContext(AppContext);
|
||||
const { fee, getFee } = useGetFee();
|
||||
|
||||
const handleOnNext = async () => {
|
||||
@@ -47,7 +47,7 @@ export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void
|
||||
try {
|
||||
const txResponse = await send({ amount: val, address: to, memo: '', fee: fee?.fee });
|
||||
setTxDetails({
|
||||
amount: `${amount?.amount} ${denom}`,
|
||||
amount: `${amount?.amount} ${clientDetails?.display_mix_denom.toUpperCase()}`,
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${txResponse.tx_hash}`,
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -74,6 +74,7 @@ export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void
|
||||
onClose={onClose}
|
||||
onPrev={() => setModal('send')}
|
||||
onSend={handleSend}
|
||||
denom={clientDetails?.display_mix_denom || 'nym'}
|
||||
{...hasStorybookStyles}
|
||||
/>
|
||||
);
|
||||
@@ -87,6 +88,7 @@ export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void
|
||||
onClose={onClose}
|
||||
onNext={handleOnNext}
|
||||
error={error}
|
||||
denom={clientDetails?.display_mix_denom}
|
||||
onAmountChange={(value) => setAmount(value)}
|
||||
onAddressChange={(value) => setToAddress(value)}
|
||||
{...hasStorybookStyles}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Stack, Typography } from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { Stack, Typography, SxProps } from '@mui/material';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { TTransactionDetails } from './types';
|
||||
import { ConfirmationModal } from '../Modals/ConfirmationModal';
|
||||
|
||||
@@ -11,7 +11,7 @@ export const TokenPoolSelector: React.FC<{ disabled: boolean; onSelect: (pool: T
|
||||
const [value, setValue] = useState<TPoolOption>('balance');
|
||||
const {
|
||||
userBalance: { tokenAllocation, balance, fetchBalance, fetchTokenAllocation },
|
||||
denom,
|
||||
clientDetails,
|
||||
} = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -48,7 +48,9 @@ export const TokenPoolSelector: React.FC<{ disabled: boolean; onSelect: (pool: T
|
||||
{tokenAllocation && (
|
||||
<ListItemText
|
||||
primary="Locked"
|
||||
secondary={`${+tokenAllocation.locked + +tokenAllocation.spendable} ${denom}`}
|
||||
secondary={`${
|
||||
+tokenAllocation.locked + +tokenAllocation.spendable
|
||||
} ${clientDetails?.display_mix_denom.toUpperCase()}`}
|
||||
sx={{ textTransform: 'uppercase' }}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -74,7 +74,6 @@ export const DelegationContextProvider: FC<{
|
||||
};
|
||||
|
||||
const resetState = () => {
|
||||
setIsLoading(true);
|
||||
setError(undefined);
|
||||
setTotalDelegations(undefined);
|
||||
setTotalRewards(undefined);
|
||||
@@ -82,6 +81,7 @@ export const DelegationContextProvider: FC<{
|
||||
};
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const data = await getDelegationSummary();
|
||||
const pending = await getAllPendingDelegations();
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { createContext, useEffect, useMemo, useState } from 'react';
|
||||
import { forage } from '@tauri-apps/tauri-forage';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { Account, AccountEntry, CurrencyDenom, MixNodeBond } from '@nymproject/types';
|
||||
import { Account, AccountEntry, MixNodeBond } from '@nymproject/types';
|
||||
import { getVersion } from '@tauri-apps/api/app';
|
||||
import { AppEnv, Network } from '../types';
|
||||
import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance';
|
||||
@@ -49,9 +49,10 @@ export type TAppContext = {
|
||||
loginType?: TLoginType;
|
||||
showSettings: boolean;
|
||||
showSendModal: boolean;
|
||||
denom: Uppercase<CurrencyDenom>;
|
||||
showReceiveModal: boolean;
|
||||
handleShowSettings: () => void;
|
||||
handleShowSendModal: () => void;
|
||||
handleShowReceiveModal: () => void;
|
||||
setIsLoading: (isLoading: boolean) => void;
|
||||
setError: (value?: string) => void;
|
||||
switchNetwork: (network: Network) => void;
|
||||
@@ -68,7 +69,6 @@ export const AppContext = createContext({} as TAppContext);
|
||||
|
||||
export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [clientDetails, setClientDetails] = useState<Account>();
|
||||
const [denom, setDenom] = useState<Uppercase<CurrencyDenom>>('NYM');
|
||||
const [storedAccounts, setStoredAccounts] = useState<AccountEntry[]>();
|
||||
const [mixnodeDetails, setMixnodeDetails] = useState<MixNodeBond | null>(null);
|
||||
const [network, setNetwork] = useState<Network | undefined>();
|
||||
@@ -83,6 +83,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [isAdminAddress, setIsAdminAddress] = useState<boolean>(false);
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [showSendModal, setShowSendModal] = useState(false);
|
||||
const [showReceiveModal, setShowReceiveModal] = useState(false);
|
||||
|
||||
const userBalance = useGetBalance(clientDetails);
|
||||
const navigate = useNavigate();
|
||||
@@ -101,7 +102,6 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
try {
|
||||
const client = await selectNetwork(n);
|
||||
setClientDetails(client);
|
||||
setDenom(client.display_mix_denom.toUpperCase() as Uppercase<CurrencyDenom>);
|
||||
} catch (e) {
|
||||
enqueueSnackbar('Error loading account', { variant: 'error' });
|
||||
Console.error(e as string);
|
||||
@@ -237,6 +237,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const switchNetwork = (_network: Network) => setNetwork(_network);
|
||||
const handleShowSettings = () => setShowSettings((show) => !show);
|
||||
const handleShowSendModal = () => setShowSendModal((show) => !show);
|
||||
const handleShowReceiveModal = () => setShowReceiveModal((show) => !show);
|
||||
const handleSwitchMode = () =>
|
||||
setMode((currentMode) => {
|
||||
const newMode = currentMode === 'light' ? 'dark' : 'light';
|
||||
@@ -256,7 +257,6 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
storedAccounts,
|
||||
mixnodeDetails,
|
||||
userBalance,
|
||||
denom,
|
||||
showAdmin,
|
||||
showTerminal,
|
||||
showSettings,
|
||||
@@ -274,7 +274,9 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
onAccountChange,
|
||||
handleShowSettings,
|
||||
showSendModal,
|
||||
showReceiveModal,
|
||||
handleShowSendModal,
|
||||
handleShowReceiveModal,
|
||||
handleSwitchMode,
|
||||
}),
|
||||
[
|
||||
@@ -294,7 +296,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
showTerminal,
|
||||
showSettings,
|
||||
showSendModal,
|
||||
denom,
|
||||
showReceiveModal,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -35,11 +35,12 @@ export const MockMainContextProvider: FC<{}> = ({ children }) => {
|
||||
fetchTokenAllocation: async () => undefined,
|
||||
refreshBalances: async () => {},
|
||||
},
|
||||
denom: 'NYM',
|
||||
displayDenom: 'NYM',
|
||||
showAdmin: false,
|
||||
showTerminal: false,
|
||||
showSettings: false,
|
||||
showSendModal: true,
|
||||
showReceiveModal: false,
|
||||
network: 'SANDBOX',
|
||||
loginType: 'mnemonic',
|
||||
setIsLoading: () => undefined,
|
||||
@@ -54,6 +55,7 @@ export const MockMainContextProvider: FC<{}> = ({ children }) => {
|
||||
onAccountChange: () => undefined,
|
||||
handleShowSettings: () => undefined,
|
||||
handleShowSendModal: () => undefined,
|
||||
handleShowReceiveModal: () => undefined,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
@@ -16,18 +16,19 @@ export const TransferModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const [fee, setFee] = useState<FeeDetails>();
|
||||
const [tx, setTx] = useState<TTransactionDetails>();
|
||||
|
||||
const { userBalance, denom, network } = useContext(AppContext);
|
||||
const { userBalance, clientDetails, network } = useContext(AppContext);
|
||||
|
||||
const getFee = async () => {
|
||||
if (userBalance.tokenAllocation?.spendable && denom) {
|
||||
if (userBalance.tokenAllocation?.spendable && clientDetails?.display_mix_denom) {
|
||||
try {
|
||||
const simulatedFee = await simulateWithdrawVestedCoins({
|
||||
amount: { amount: userBalance.tokenAllocation?.spendable, denom },
|
||||
amount: { amount: userBalance.tokenAllocation?.spendable, denom: clientDetails?.display_mix_denom },
|
||||
});
|
||||
setFee(simulatedFee);
|
||||
await userBalance.refreshBalances();
|
||||
} catch (e) {
|
||||
setFee({
|
||||
amount: { amount: 'n/a', denom: denom as CurrencyDenom },
|
||||
amount: { amount: 'n/a', denom: clientDetails?.display_mix_denom.toUpperCase() as CurrencyDenom },
|
||||
fee: { Auto: null },
|
||||
});
|
||||
Console.error(e);
|
||||
@@ -40,16 +41,19 @@ export const TransferModal = ({ onClose }: { onClose: () => void }) => {
|
||||
}, []);
|
||||
|
||||
const handleTransfer = async () => {
|
||||
if (userBalance.tokenAllocation?.spendable && denom) {
|
||||
if (userBalance.tokenAllocation?.spendable && clientDetails?.display_mix_denom) {
|
||||
setState('loading');
|
||||
try {
|
||||
const txResponse = await withdrawVestedCoins({
|
||||
amount: userBalance.tokenAllocation?.spendable,
|
||||
denom: denom as CurrencyDenom,
|
||||
});
|
||||
const txResponse = await withdrawVestedCoins(
|
||||
{
|
||||
amount: userBalance.tokenAllocation?.spendable,
|
||||
denom: clientDetails?.display_mix_denom,
|
||||
},
|
||||
fee?.fee,
|
||||
);
|
||||
setState('success');
|
||||
setTx({
|
||||
amount: `${userBalance.tokenAllocation?.spendable} ${denom}`,
|
||||
amount: `${userBalance.tokenAllocation?.spendable} ${clientDetails.display_mix_denom.toUpperCase()}`,
|
||||
url: `${urls(network).blockExplorer}/transaction/${txResponse.transaction_hash}`,
|
||||
});
|
||||
await userBalance.refreshBalances();
|
||||
@@ -84,7 +88,7 @@ export const TransferModal = ({ onClose }: { onClose: () => void }) => {
|
||||
<>
|
||||
<ModalListItem
|
||||
label="Unlocked transferrable tokens"
|
||||
value={`${userBalance.tokenAllocation?.spendable} ${denom}`}
|
||||
value={`${userBalance.tokenAllocation?.spendable} ${clientDetails?.display_mix_denom.toUpperCase()}`}
|
||||
divider
|
||||
/>
|
||||
<ModalListItem
|
||||
|
||||
@@ -35,7 +35,7 @@ const vestingPeriod = (current?: Period, original?: number) => {
|
||||
};
|
||||
|
||||
const VestingSchedule = () => {
|
||||
const { userBalance, denom } = useContext(AppContext);
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
const [vestedPercentage, setVestedPercentage] = useState(0);
|
||||
|
||||
const calculatePercentage = () => {
|
||||
@@ -66,7 +66,8 @@ const VestingSchedule = () => {
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell sx={{ borderBottom: 'none', textTransform: 'uppercase' }}>
|
||||
{userBalance.tokenAllocation?.vesting || 'n/a'} / {userBalance.originalVesting?.amount.amount} {denom}
|
||||
{userBalance.tokenAllocation?.vesting || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '}
|
||||
{clientDetails?.display_mix_denom.toUpperCase()}
|
||||
</TableCell>
|
||||
<TableCell align="left" sx={{ borderBottom: 'none' }}>
|
||||
{vestingPeriod(userBalance.currentVestingPeriod, userBalance.originalVesting?.number_of_periods)}
|
||||
@@ -78,7 +79,8 @@ const VestingSchedule = () => {
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell sx={{ borderBottom: 'none', textTransform: 'uppercase' }} align="right">
|
||||
{userBalance.tokenAllocation?.vested || 'n/a'} / {userBalance.originalVesting?.amount.amount} {denom}
|
||||
{userBalance.tokenAllocation?.vested || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '}
|
||||
{clientDetails?.display_mix_denom.toUpperCase()}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
@@ -88,7 +90,7 @@ const VestingSchedule = () => {
|
||||
};
|
||||
|
||||
const TokenTransfer = () => {
|
||||
const { userBalance, denom } = useContext(AppContext);
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
const icon = useCallback(
|
||||
() => (
|
||||
<Box sx={{ display: 'flex', mr: 1 }}>
|
||||
@@ -114,7 +116,7 @@ const TokenTransfer = () => {
|
||||
fontWeight="700"
|
||||
textTransform="uppercase"
|
||||
>
|
||||
{userBalance.tokenAllocation?.spendable || 'n/a'} {denom}
|
||||
{userBalance.tokenAllocation?.spendable || 'n/a'} {clientDetails?.display_mix_denom.toUpperCase()}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -63,7 +63,7 @@ export const GatewayForm = ({
|
||||
resolver: yupResolver(gatewayValidationSchema),
|
||||
defaultValues,
|
||||
});
|
||||
const { userBalance, clientDetails, denom } = useContext(AppContext);
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
|
||||
const { fee, getFee, resetFeeState, feeError } = useGetFee();
|
||||
|
||||
@@ -209,7 +209,7 @@ export const GatewayForm = ({
|
||||
fullWidth
|
||||
label="Amount"
|
||||
onChanged={(val) => setValue('amount', val, { shouldValidate: true })}
|
||||
denom={denom}
|
||||
denom={clientDetails?.display_mix_denom}
|
||||
validationError={errors.amount?.amount?.message}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -66,7 +66,7 @@ export const MixnodeForm = ({
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const { userBalance, clientDetails, denom } = useContext(AppContext);
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
|
||||
const { fee, getFee, resetFeeState, feeError } = useGetFee();
|
||||
|
||||
@@ -216,7 +216,7 @@ export const MixnodeForm = ({
|
||||
fullWidth
|
||||
label="Amount"
|
||||
onChanged={(val) => setValue('amount', val, { shouldValidate: true })}
|
||||
denom={denom}
|
||||
denom={clientDetails?.display_mix_denom}
|
||||
validationError={errors.amount?.amount?.message}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { AppContext } from '../../../context/main';
|
||||
import { useCheckOwnership } from '../../../hooks/useCheckOwnership';
|
||||
|
||||
export const SuccessView: React.FC<{ details?: { amount: string; address: string } }> = ({ details }) => {
|
||||
const { userBalance, denom } = useContext(AppContext);
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
const { ownership } = useCheckOwnership();
|
||||
|
||||
return (
|
||||
@@ -15,7 +15,9 @@ export const SuccessView: React.FC<{ details?: { amount: string; address: string
|
||||
subtitle="Successfully bonded to node with following details"
|
||||
caption={
|
||||
ownership.vestingPledge
|
||||
? `Your current locked balance is: ${userBalance.tokenAllocation?.locked}${denom}`
|
||||
? `Your current locked balance is: ${
|
||||
userBalance.tokenAllocation?.locked
|
||||
} ${clientDetails?.display_mix_denom.toUpperCase()}`
|
||||
: `Your current balance is: ${userBalance.balance?.printable_balance.toUpperCase()}`
|
||||
}
|
||||
/>
|
||||
@@ -26,7 +28,7 @@ export const SuccessView: React.FC<{ details?: { amount: string; address: string
|
||||
{ primary: 'Node', secondary: details.address },
|
||||
{
|
||||
primary: 'Amount',
|
||||
secondary: `${details.amount} ${denom}`,
|
||||
secondary: `${details.amount} ${clientDetails?.display_mix_denom.toUpperCase()}`,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -44,7 +44,6 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
const {
|
||||
clientDetails,
|
||||
network,
|
||||
denom,
|
||||
userBalance: { balance, originalVesting, fetchBalance },
|
||||
} = useContext(AppContext);
|
||||
|
||||
@@ -343,7 +342,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
onOk={handleNewDelegation}
|
||||
header="Delegate"
|
||||
buttonText="Delegate stake"
|
||||
currency={denom}
|
||||
denom={clientDetails?.display_mix_denom || 'nym'}
|
||||
accountBalance={balance?.printable_balance}
|
||||
rewardInterval="weekly"
|
||||
hasVestingContract={Boolean(originalVesting)}
|
||||
@@ -359,7 +358,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
header="Delegate more"
|
||||
buttonText="Delegate more"
|
||||
identityKey={currentDelegationListActionItem.node_identity}
|
||||
currency={denom}
|
||||
denom={clientDetails?.display_mix_denom || 'nym'}
|
||||
accountBalance={balance?.printable_balance}
|
||||
nodeUptimePercentage={currentDelegationListActionItem.avg_uptime_percent}
|
||||
profitMarginPercentage={currentDelegationListActionItem.profit_margin_percent}
|
||||
@@ -386,7 +385,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
onClose={() => setShowRedeemRewardsModal(false)}
|
||||
onOk={(identity, fee) => handleRedeem(identity, fee)}
|
||||
message="Redeem rewards"
|
||||
currency={denom}
|
||||
denom={clientDetails?.display_mix_denom || 'nym'}
|
||||
identityKey={currentDelegationListActionItem?.node_identity}
|
||||
amount={+currentDelegationListActionItem.accumulated_rewards.amount}
|
||||
usesVestingTokens={currentDelegationListActionItem.uses_vesting_contract_tokens}
|
||||
@@ -399,7 +398,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
onClose={() => setShowCompoundRewardsModal(false)}
|
||||
onOk={(identity, fee) => handleCompound(identity, fee)}
|
||||
message="Compound rewards"
|
||||
currency={denom}
|
||||
denom={clientDetails?.display_mix_denom || 'nym'}
|
||||
identityKey={currentDelegationListActionItem?.node_identity}
|
||||
amount={+currentDelegationListActionItem.accumulated_rewards.amount}
|
||||
usesVestingTokens={currentDelegationListActionItem.uses_vesting_contract_tokens}
|
||||
|
||||
@@ -2,7 +2,6 @@ export * from './Admin';
|
||||
export * from './balance';
|
||||
export * from './bond';
|
||||
export * from './internal-docs';
|
||||
export * from './receive';
|
||||
export * from './auth';
|
||||
export * from './settings';
|
||||
export * from './unbond';
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import React, { useContext } from 'react';
|
||||
import QRCode from 'qrcode.react';
|
||||
import { Alert, Box, Stack } from '@mui/material';
|
||||
import { ClientAddress, NymCard } from '../../components';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { PageLayout } from '../../layouts';
|
||||
|
||||
export const Receive = () => {
|
||||
const { clientDetails, denom } = useContext(AppContext);
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<NymCard title={`Receive ${denom}`}>
|
||||
<Stack spacing={3} alignItems="center">
|
||||
<Alert severity="info" data-testid="receive-nym" sx={{ width: '100%' }}>
|
||||
You can receive tokens by providing this address to the sender
|
||||
</Alert>
|
||||
<Box>
|
||||
<ClientAddress withCopy showEntireAddress />
|
||||
</Box>
|
||||
{clientDetails && <QRCode data-testid="qr-code" value={clientDetails?.client_address} />}
|
||||
</Stack>
|
||||
</NymCard>
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
@@ -57,8 +57,8 @@ export const vestingBondMixNode = async ({
|
||||
export const vestingUnbondMixnode = async (fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_unbond_mixnode', { fee });
|
||||
|
||||
export const withdrawVestedCoins = async (amount: DecCoin) =>
|
||||
invokeWrapper<TransactionExecuteResult>('withdraw_vested_coins', { amount });
|
||||
export const withdrawVestedCoins = async (amount: DecCoin, fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('withdraw_vested_coins', { amount, fee });
|
||||
|
||||
export const vestingUpdateMixnode = async (profitMarginPercent: number) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_update_mixnode', { profitMarginPercent });
|
||||
|
||||
@@ -3,16 +3,17 @@ import { Route, Routes } from 'react-router-dom';
|
||||
import { ApplicationLayout } from 'src/layouts';
|
||||
import { Terminal } from 'src/pages/terminal';
|
||||
import { Send } from 'src/components/Send';
|
||||
import { Bond, Balance, InternalDocs, Receive, Unbond, DelegationPage, Admin, Settings } from '../pages';
|
||||
import { Receive } from '../components/Receive';
|
||||
import { Bond, Balance, InternalDocs, Unbond, DelegationPage, Admin, Settings } from '../pages';
|
||||
|
||||
export const AppRoutes = () => (
|
||||
<ApplicationLayout>
|
||||
<Terminal />
|
||||
<Settings />
|
||||
<Send />
|
||||
<Receive />
|
||||
<Routes>
|
||||
<Route path="/balance" element={<Balance />} />
|
||||
<Route path="/receive" element={<Receive />} />
|
||||
<Route path="/bond" element={<Bond />} />
|
||||
<Route path="/unbond" element={<Unbond />} />
|
||||
<Route path="/delegation" element={<DelegationPage />} />
|
||||
|
||||
@@ -109,9 +109,14 @@ impl OutboundRequestFilter {
|
||||
}
|
||||
|
||||
/// Attempts to get the root domain, shorn of subdomains, using publicsuffix.
|
||||
/// If the domain is itself a suffix, then just use the full address as root.
|
||||
fn get_domain_root(&self, host: &str) -> Option<String> {
|
||||
match self.domain_list.parse_domain(host) {
|
||||
Ok(d) => d.root().map(|root| root.to_string()),
|
||||
Ok(d) => Some(
|
||||
d.root()
|
||||
.map(|root| root.to_string())
|
||||
.unwrap_or_else(|| d.full().to_string()),
|
||||
),
|
||||
Err(_) => {
|
||||
log::warn!("Error parsing domain: {:?}", host);
|
||||
None // domain couldn't be parsed
|
||||
@@ -348,9 +353,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_none_on_nonsense_domains() {
|
||||
fn returns_full_on_suffix_domains() {
|
||||
let filter = setup();
|
||||
assert_eq!(None, filter.get_domain_root("flappappa"));
|
||||
assert_eq!(
|
||||
Some("s3.amazonaws.com".to_string()),
|
||||
filter.get_domain_root("s3.amazonaws.com")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -194,17 +194,10 @@ impl StatisticsCollector for ServiceStatisticsCollector {
|
||||
}
|
||||
|
||||
async fn reset_stats(&mut self) {
|
||||
self.request_stats_data
|
||||
.write()
|
||||
.await
|
||||
.client_processed_bytes
|
||||
.iter_mut()
|
||||
.for_each(|(_, b)| *b = 0);
|
||||
self.request_stats_data.write().await.client_processed_bytes = HashMap::new();
|
||||
self.response_stats_data
|
||||
.write()
|
||||
.await
|
||||
.client_processed_bytes
|
||||
.iter_mut()
|
||||
.for_each(|(_, b)| *b = 0);
|
||||
.client_processed_bytes = HashMap::new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ CREATE TABLE service_statistics
|
||||
CREATE TABLE gateway_statistics
|
||||
(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
gateway_id VARCHAR NOT NULL,
|
||||
inbox_count INTEGER NOT NULL,
|
||||
timestamp DATETIME NOT NULL
|
||||
);
|
||||
@@ -35,6 +35,7 @@ pub struct ServiceStatistic {
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct GatewayStatistic {
|
||||
pub gateway_id: String,
|
||||
pub inbox_count: u32,
|
||||
pub timestamp: String,
|
||||
}
|
||||
@@ -70,6 +71,7 @@ pub(crate) async fn post_all_statistics(
|
||||
.into_iter()
|
||||
.map(|data| {
|
||||
GenericStatistic::Gateway(GatewayStatistic {
|
||||
gateway_id: data.gateway_id,
|
||||
inbox_count: data.inbox_count as u32,
|
||||
timestamp: data.timestamp.to_string(),
|
||||
})
|
||||
|
||||
@@ -52,11 +52,13 @@ impl StorageManager {
|
||||
/// * `timestamp`: The moment in time when the data started being collected.
|
||||
pub(super) async fn insert_gateway_statistics(
|
||||
&self,
|
||||
gateway_id: String,
|
||||
inbox_count: u32,
|
||||
timestamp: DateTime<Utc>,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO gateway_statistics(inbox_count, timestamp) VALUES (?, ?)",
|
||||
"INSERT INTO gateway_statistics(gateway_id, inbox_count, timestamp) VALUES (?, ?, ?)",
|
||||
gateway_id,
|
||||
inbox_count,
|
||||
timestamp,
|
||||
)
|
||||
|
||||
@@ -71,7 +71,11 @@ impl NetworkStatisticsStorage {
|
||||
}
|
||||
statistics_common::StatsData::Gateway(gateway_data) => {
|
||||
self.manager
|
||||
.insert_gateway_statistics(gateway_data.inbox_count, timestamp)
|
||||
.insert_gateway_statistics(
|
||||
gateway_data.gateway_id,
|
||||
gateway_data.inbox_count,
|
||||
timestamp,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ pub(crate) struct ServiceStatistics {
|
||||
pub(crate) struct GatewayStatistics {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) id: i64,
|
||||
pub(crate) gateway_id: String,
|
||||
pub(crate) inbox_count: i64,
|
||||
pub(crate) timestamp: NaiveDateTime,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "validator-client-scripts"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.13.0"
|
||||
bs58 = "0.4"
|
||||
clap = { version = "3.2.8", features = ["derive"] }
|
||||
dotenv = "0.15.0"
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tokio = { version = "1.11", features = [ "net", "rt-multi-thread", "macros", "signal"] }
|
||||
bip39 = "1.0.1"
|
||||
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
|
||||
network-defaults = { path = "../../common/network-defaults" }
|
||||
mixnet-contract-common ={ path = "../../common/cosmwasm-smart-contracts/mixnet-contract" }
|
||||
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" }
|
||||
@@ -0,0 +1,32 @@
|
||||
# NYM - CLI TOOL
|
||||
## Overview
|
||||
Set of simple CLI commands to perform basic actions, such as upload contract, bond mixnode, etc.
|
||||
Note: to work it requires rust 1.60+
|
||||
|
||||
## The binary
|
||||
- Build the binary
|
||||
- cargo build --release --bin validator-client-scripts
|
||||
- this will compile the binary to "your/main/nym/path/target/release"
|
||||
- the binary is named ./validator-client-scripts
|
||||
|
||||
## Executing commands
|
||||
-./validator-client-scripts --help
|
||||
- will produce a list of it's capabilites
|
||||
|
||||
The binary takes optional args which are as follows -> OPTIONS:
|
||||
```
|
||||
--config-env-file <CONFIG_ENV_FILE>
|
||||
--mixnet-contract <MIXNET_CONTRACT>
|
||||
--mnemonic <MNEMONIC>
|
||||
--nymd-url <NYMD_URL>
|
||||
--vesting-contract <VESTING_CONTRACT>
|
||||
```
|
||||
If you specify --config-env-file it will read the values from the envs/directory:
|
||||
`./validator-client-scripts --config-env-file env/qa.env .....` and you don't need to supply the
|
||||
mixnet-contract nor the vesting-contract argument
|
||||
|
||||
An example of a command is as follows:
|
||||
```
|
||||
Disclaimer the amount is in UNYMs
|
||||
./validator-client-scripts --config-env-file ../../envs/qa.env --nymd-url https://qa-validator.nymtech.net --mnemonic "INPUT YOUR MNEMONIC" send --amount 100000000 --recipient <NYM_ADDRESS>
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
CONFIGURED=true
|
||||
|
||||
RUST_LOG=info
|
||||
RUST_BACKTRACE=1
|
||||
|
||||
BECH32_PREFIX=n
|
||||
MIX_DENOM=unym
|
||||
MIX_DENOM_DISPLAY=nym
|
||||
STAKE_DENOM=unyx
|
||||
STAKE_DENOM_DISPLAY=nyx
|
||||
DENOMS_EXPONENT=6
|
||||
MIXNET_CONTRACT_ADDRESS=n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g
|
||||
VESTING_CONTRACT_ADDRESS=n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw
|
||||
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
MULTISIG_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS="http://127.0.0.1:8090"
|
||||
NYMD_VALIDATOR="https://rpc.nyx.nodes.guru/"
|
||||
API_VALIDATOR="https://validator.nymtech.net/api/"
|
||||
@@ -0,0 +1,20 @@
|
||||
CONFIGURED=true
|
||||
|
||||
RUST_LOG=info
|
||||
RUST_BACKTRACE=1
|
||||
|
||||
BECH32_PREFIX=n
|
||||
MIX_DENOM=unym
|
||||
MIX_DENOM_DISPLAY=nym
|
||||
STAKE_DENOM=unyx
|
||||
STAKE_DENOM_DISPLAY=nyx
|
||||
DENOMS_EXPONENT=6
|
||||
MIXNET_CONTRACT_ADDRESS=n1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsd3qaep
|
||||
VESTING_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav
|
||||
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw
|
||||
MULTISIG_CONTRACT_ADDRESS=n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs
|
||||
REWARDING_VALIDATOR_ADDRESS=n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0"
|
||||
NYMD_VALIDATOR="https://qa-validator.nymtech.net"
|
||||
API_VALIDATOR="https://qa-validator-api.nymtech.net/api"
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::Client;
|
||||
use clap::Parser;
|
||||
use log::{info, warn};
|
||||
use mixnet_contract_common::Coin;
|
||||
use network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub(crate) struct Args {
|
||||
#[clap(long)]
|
||||
pub host: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub signature: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub mix_port: Option<u16>,
|
||||
|
||||
#[clap(long)]
|
||||
pub clients_port: Option<u16>,
|
||||
|
||||
#[clap(long)]
|
||||
pub location: Option<String>,
|
||||
|
||||
#[clap(long)]
|
||||
pub sphinx_key: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub identity_key: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub version: String,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
help = "bonding amount in current DENOMINATION (so it would be 'unym', rather than 'nym')"
|
||||
)]
|
||||
pub amount: u128,
|
||||
|
||||
#[clap(long)]
|
||||
pub gas: Option<u64>,
|
||||
|
||||
#[clap(short, long)]
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
pub(crate) async fn bond_gateway(client: Client, args: Args, denom: &str) {
|
||||
info!("Starting gateway bonding!");
|
||||
|
||||
// if we're trying to bond less than 1 token
|
||||
if args.amount < 1_000_000 && !args.force {
|
||||
warn!("You're trying to bond only {}{} which is less than 1 full token. Are you sure that's what you want? If so, run with `--force` or `-f` flag", args.amount, denom);
|
||||
return;
|
||||
}
|
||||
|
||||
let gateway = mixnet_contract_common::Gateway {
|
||||
host: args.host,
|
||||
mix_port: args.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT),
|
||||
clients_port: args.clients_port.unwrap_or(DEFAULT_CLIENT_LISTENING_PORT),
|
||||
location: args
|
||||
.location
|
||||
.unwrap_or_else(|| "secret gateway location".to_owned()),
|
||||
sphinx_key: args.sphinx_key,
|
||||
identity_key: args.identity_key,
|
||||
version: args.version,
|
||||
};
|
||||
|
||||
let coin = Coin::new(args.amount, denom);
|
||||
|
||||
let res = client
|
||||
.bond_gateway(gateway, args.signature, coin.into(), None)
|
||||
.await
|
||||
.expect("failed to bond gateway!");
|
||||
|
||||
info!("Bonding result: {:?}", res)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::Client;
|
||||
use clap::Parser;
|
||||
use log::{info, warn};
|
||||
use mixnet_contract_common::Coin;
|
||||
use network_defaults::{
|
||||
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
|
||||
};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub(crate) struct Args {
|
||||
#[clap(long)]
|
||||
pub host: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub signature: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub mix_port: Option<u16>,
|
||||
|
||||
#[clap(long)]
|
||||
pub verloc_port: Option<u16>,
|
||||
|
||||
#[clap(long)]
|
||||
pub http_api_port: Option<u16>,
|
||||
|
||||
#[clap(long)]
|
||||
pub sphinx_key: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub identity_key: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub version: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub profit_margin_percent: Option<u8>,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
help = "bonding amount in current DENOMINATION (so it would be 'unym', rather than 'nym')"
|
||||
)]
|
||||
pub amount: u128,
|
||||
|
||||
#[clap(long)]
|
||||
pub gas: Option<u64>,
|
||||
|
||||
#[clap(short, long)]
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
pub(crate) async fn bond_mixnode(client: Client, args: Args, denom: &str) {
|
||||
info!("Starting mixnode bonding!");
|
||||
|
||||
// if we're trying to bond less than 1 token
|
||||
if args.amount < 1_000_000 && !args.force {
|
||||
warn!("You're trying to bond only {}{} which is less than 1 full token. Are you sure that's what you want? If so, run with `--force` or `-f` flag", args.amount, denom);
|
||||
return;
|
||||
}
|
||||
|
||||
let mixnode = mixnet_contract_common::MixNode {
|
||||
host: args.host,
|
||||
mix_port: args.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT),
|
||||
verloc_port: args.verloc_port.unwrap_or(DEFAULT_VERLOC_LISTENING_PORT),
|
||||
http_api_port: args
|
||||
.http_api_port
|
||||
.unwrap_or(DEFAULT_HTTP_API_LISTENING_PORT),
|
||||
sphinx_key: args.sphinx_key,
|
||||
identity_key: args.identity_key,
|
||||
version: args.version,
|
||||
profit_margin_percent: args.profit_margin_percent.unwrap_or(10),
|
||||
};
|
||||
|
||||
let coin = Coin::new(args.amount, denom);
|
||||
|
||||
let res = client
|
||||
.bond_mixnode(mixnode, args.signature, coin.into(), None)
|
||||
.await
|
||||
.expect("failed to bond mixnode!");
|
||||
|
||||
info!("Bonding result: {:?}", res)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::Client;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub(crate) struct Args {
|
||||
#[clap(long)]
|
||||
pub gas: Option<u64>,
|
||||
|
||||
#[clap(long)]
|
||||
pub identity: String,
|
||||
}
|
||||
|
||||
pub(crate) async fn claim_delegator_reward(client: Client, args: Args) {
|
||||
info!("Claim delegator reward");
|
||||
|
||||
let res = client
|
||||
.execute_claim_delegator_reward(args.identity, None)
|
||||
.await
|
||||
.expect("failed to claim delegator-reward");
|
||||
|
||||
info!("Claiming delegator reward: {:?}", res)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::Client;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub(crate) struct Args {
|
||||
#[clap(long)]
|
||||
pub gas: Option<u64>,
|
||||
}
|
||||
|
||||
pub(crate) async fn claim_operator_reward(client: Client) {
|
||||
info!("Claim operator reward");
|
||||
|
||||
let res = client
|
||||
.execute_claim_operator_reward(None)
|
||||
.await
|
||||
.expect("failed to claim operator reward");
|
||||
|
||||
info!("Claiming operator reward: {:?}", res)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::Client;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub(crate) struct Args {
|
||||
#[clap(long)]
|
||||
pub gas: Option<u64>,
|
||||
|
||||
#[clap(long)]
|
||||
pub identity: String,
|
||||
}
|
||||
|
||||
pub(crate) async fn compound_delegator_reward(client: Client, args: Args) {
|
||||
info!("compound delegator reward");
|
||||
|
||||
let res = client
|
||||
.execute_compound_delegator_reward(args.identity, None)
|
||||
.await
|
||||
.expect("failed to claim delegator-reward");
|
||||
|
||||
info!("Claiming delegator reward: {:?}", res)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::Client;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub(crate) struct Args {
|
||||
#[clap(long)]
|
||||
pub gas: Option<u64>,
|
||||
}
|
||||
|
||||
pub(crate) async fn compound_operator_reward(client: Client, _args: Args) {
|
||||
info!("compounding operator reward");
|
||||
|
||||
let res = client
|
||||
.execute_compound_operator_reward(None)
|
||||
.await
|
||||
.expect("failed to compound operator-reward");
|
||||
|
||||
info!("Claiming compound operator reward: {:?}", res)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use validator_client::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub(crate) struct Args {
|
||||
#[clap(long)]
|
||||
// allowed values are 12, 18 or 24
|
||||
pub word_count: Option<usize>,
|
||||
}
|
||||
|
||||
pub(crate) fn create_account(args: Args, prefix: &str) {
|
||||
let word_count = args.word_count.unwrap_or(24);
|
||||
let mnemonic = bip39::Mnemonic::generate(word_count).expect("failed to generate mnemonic!");
|
||||
|
||||
let wallet =
|
||||
DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic).expect("failed to build wallet!");
|
||||
|
||||
// Output address and mnemonics into separate lines for easier parsing
|
||||
println!("{}", wallet.mnemonic());
|
||||
println!("{}", wallet.try_derive_accounts().unwrap()[0].address());
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub(crate) struct Args {
|
||||
#[clap(short, long)]
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
pub(crate) fn decode_mixnode_key(args: Args) {
|
||||
let b64_decoded = base64::decode(args.key).expect("failed to decode base64 string");
|
||||
let b58_encoded = bs58::encode(&b64_decoded).into_string();
|
||||
|
||||
println!("{}", b58_encoded)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::Client;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use mixnet_contract_common::Coin;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub(crate) struct Args {
|
||||
#[clap(long)]
|
||||
pub gas: Option<u64>,
|
||||
|
||||
#[clap(long)]
|
||||
pub identity_key: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub amount: u128,
|
||||
}
|
||||
|
||||
pub(crate) async fn delegate_to_mixnode(client: Client, args: Args, denom: &str) {
|
||||
info!("Starting delegation to mixnode");
|
||||
|
||||
let coin = Coin::new(args.amount, denom);
|
||||
|
||||
let res = client
|
||||
.delegate_to_mixnode(&*args.identity_key, coin.into(), None)
|
||||
.await
|
||||
.expect("failed to delegate to mixnode!");
|
||||
|
||||
info!("delegating to mixnode: {:?}", res);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::Client;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use mixnet_contract_common::InstantiateMsg;
|
||||
use validator_client::nymd::cosmwasm_client::types::{ContractCodeId, InstantiateOptions};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub(crate) struct Args {
|
||||
#[clap(long)]
|
||||
pub code_id: ContractCodeId,
|
||||
|
||||
#[clap(long)]
|
||||
pub memo: Option<String>,
|
||||
|
||||
#[clap(long)]
|
||||
pub label: Option<String>,
|
||||
|
||||
#[clap(long)]
|
||||
pub init_message: Option<String>,
|
||||
|
||||
#[clap(long)]
|
||||
pub gas: Option<u64>,
|
||||
}
|
||||
|
||||
pub(crate) async fn init(client: Client, args: Args, denom: &str) {
|
||||
info!("Starting contract instantiation!");
|
||||
|
||||
let memo = args
|
||||
.memo
|
||||
.unwrap_or_else(|| "contract instantiation".to_owned());
|
||||
let label = args
|
||||
.label
|
||||
.unwrap_or_else(|| "Nym mixnet smart contract".to_owned());
|
||||
|
||||
// by default we make ourselves an admin, let me know if you don't like that behaviour
|
||||
let opts = Some(InstantiateOptions {
|
||||
funds: vec![],
|
||||
admin: Some(client.address().clone()),
|
||||
});
|
||||
|
||||
// the EmptyMsg{} argument is equivalent to `--init-message='{}'`
|
||||
let res = if let Some(raw_msg) = args.init_message {
|
||||
let msg: serde_json::Value =
|
||||
serde_json::from_str(&raw_msg).expect("failed to parse init message");
|
||||
|
||||
client
|
||||
.instantiate(args.code_id, &msg, label, memo, opts, None)
|
||||
.await
|
||||
.expect("failed to instantiate the contract!")
|
||||
} else {
|
||||
let address = client.address().to_string();
|
||||
client
|
||||
.instantiate(
|
||||
args.code_id,
|
||||
&InstantiateMsg {
|
||||
rewarding_validator_address: address,
|
||||
mixnet_denom: denom.to_string(),
|
||||
},
|
||||
label,
|
||||
memo,
|
||||
opts,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("failed to instantiate the contract!")
|
||||
};
|
||||
|
||||
info!("Init result: {:?}", res);
|
||||
|
||||
println!("{}", res.contract_address)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user