Merge branch 'develop' into 348-bonding-settings
This commit is contained in:
+2
-1
@@ -6,15 +6,16 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
### Added
|
||||
|
||||
- nym-cli: added CLI tool for interacting with the Nyx blockchain and Nym mixnet smart contracts ([#1577])
|
||||
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
|
||||
|
||||
[#1541]: https://github.com/nymtech/nym/pull/1541
|
||||
[#1558]: https://github.com/nymtech/nym/pull/1558
|
||||
[#1577]: https://github.com/nymtech/nym/pull/1577
|
||||
|
||||
|
||||
## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2)
|
||||
|
||||
Generated
+1068
-653
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,7 @@ members = [
|
||||
"common/client-libs/mixnet-client",
|
||||
"common/client-libs/validator-client",
|
||||
"common/coconut-interface",
|
||||
"common/commands",
|
||||
"common/config",
|
||||
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
|
||||
"common/cosmwasm-smart-contracts/contracts-common",
|
||||
@@ -69,6 +70,7 @@ members = [
|
||||
"service-providers/network-statistics",
|
||||
"validator-api",
|
||||
"validator-api/validator-api-requests",
|
||||
"tools/nym-cli",
|
||||
"tools/ts-rs-cli"
|
||||
]
|
||||
|
||||
|
||||
@@ -69,6 +69,9 @@ build-wallet:
|
||||
build-connect:
|
||||
cargo build --manifest-path nym-connect/Cargo.toml --workspace
|
||||
|
||||
build-nym-cli:
|
||||
cargo build --release --manifest-path tools/nym-cli/Cargo.toml
|
||||
|
||||
fmt-main:
|
||||
cargo fmt --all
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ log = "0.4"
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
sled = "0.34"
|
||||
thiserror = "1.0.34"
|
||||
tokio = { version = "1.19.1", features = ["macros"] }
|
||||
url = { version ="2.2", features = ["serde"] }
|
||||
|
||||
@@ -27,6 +28,7 @@ nymsphinx = { path = "../../common/nymsphinx" }
|
||||
pemstore = { path = "../../common/pemstore" }
|
||||
topology = { path = "../../common/topology" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
tap = "1.0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crypto::asymmetric::identity::Ed25519RecoveryError;
|
||||
use gateway_client::error::GatewayClientError;
|
||||
use validator_client::ValidatorClientError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ClientCoreError {
|
||||
#[error("I/O error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("Gateway client error: {0}")]
|
||||
GatewayClientError(#[from] GatewayClientError),
|
||||
#[error("Ed25519 error: {0}")]
|
||||
Ed25519RecoveryError(#[from] Ed25519RecoveryError),
|
||||
#[error("Validator client error: {0}")]
|
||||
ValidatorClientError(#[from] ValidatorClientError),
|
||||
|
||||
#[error("No gateway with id: {0}")]
|
||||
NoGatewayWithId(String),
|
||||
#[error("No gateways on network")]
|
||||
NoGatewaysOnNetwork,
|
||||
#[error("List of validator apis is empty")]
|
||||
ListOfValidatorApisIsEmpty,
|
||||
#[error("Could not load existing gateway configuration: {0}")]
|
||||
CouldNotLoadExistingGatewayConfiguration(std::io::Error),
|
||||
}
|
||||
@@ -14,25 +14,27 @@ use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use tap::TapFallible;
|
||||
use topology::{filter::VersionFilterable, gateway};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
client::key_manager::KeyManager,
|
||||
config::{persistence::key_pathfinder::ClientKeyPathfinder, Config},
|
||||
error::ClientCoreError,
|
||||
};
|
||||
|
||||
pub async fn query_gateway_details(
|
||||
validator_servers: Vec<Url>,
|
||||
chosen_gateway_id: Option<&str>,
|
||||
) -> gateway::Node {
|
||||
) -> Result<gateway::Node, ClientCoreError> {
|
||||
let validator_api = validator_servers
|
||||
.choose(&mut thread_rng())
|
||||
.expect("The list of validator apis is empty");
|
||||
.ok_or(ClientCoreError::ListOfValidatorApisIsEmpty)?;
|
||||
let validator_client = validator_client::ApiClient::new(validator_api.clone());
|
||||
|
||||
log::trace!("Fetching list of gateways from: {}", validator_api);
|
||||
let gateways = validator_client.get_cached_gateways().await.unwrap();
|
||||
let gateways = validator_client.get_cached_gateways().await?;
|
||||
let valid_gateways = gateways
|
||||
.into_iter()
|
||||
.filter_map(|gateway| gateway.try_into().ok())
|
||||
@@ -47,38 +49,40 @@ pub async fn query_gateway_details(
|
||||
filtered_gateways
|
||||
.iter()
|
||||
.find(|gateway| gateway.identity_key.to_base58_string() == gateway_id)
|
||||
.expect(&*format!("no gateway with id {} exists!", gateway_id))
|
||||
.clone()
|
||||
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_id.to_string()))
|
||||
.cloned()
|
||||
} else {
|
||||
filtered_gateways
|
||||
.choose(&mut rand::thread_rng())
|
||||
.expect("there are no gateways on the network!")
|
||||
.clone()
|
||||
.ok_or(ClientCoreError::NoGatewaysOnNetwork)
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn register_with_gateway_and_store_keys<T>(
|
||||
gateway_details: gateway::Node,
|
||||
config: &Config<T>,
|
||||
) where
|
||||
) -> Result<(), ClientCoreError>
|
||||
where
|
||||
T: NymConfig,
|
||||
{
|
||||
let mut rng = OsRng;
|
||||
let mut key_manager = KeyManager::new(&mut rng);
|
||||
|
||||
let shared_keys = register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
|
||||
let shared_keys =
|
||||
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await?;
|
||||
key_manager.insert_gateway_shared_key(shared_keys);
|
||||
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config);
|
||||
key_manager
|
||||
Ok(key_manager
|
||||
.store_keys(&pathfinder)
|
||||
.expect("Failed to generated keys");
|
||||
.tap_err(|err| log::error!("Failed to generate keys: {err}"))?)
|
||||
}
|
||||
|
||||
async fn register_with_gateway(
|
||||
gateway: &gateway::Node,
|
||||
our_identity: Arc<identity::KeyPair>,
|
||||
) -> Arc<SharedKeys> {
|
||||
) -> Result<Arc<SharedKeys>, ClientCoreError> {
|
||||
let timeout = Duration::from_millis(1500);
|
||||
let mut gateway_client = GatewayClient::new_init(
|
||||
gateway.clients_address(),
|
||||
@@ -90,48 +94,54 @@ async fn register_with_gateway(
|
||||
gateway_client
|
||||
.establish_connection()
|
||||
.await
|
||||
.expect("failed to establish connection with the gateway!");
|
||||
gateway_client
|
||||
.tap_err(|_| log::warn!("Failed to establish connection with gateway!"))?;
|
||||
let shared_keys = gateway_client
|
||||
.perform_initial_authentication()
|
||||
.await
|
||||
.expect("failed to register with the gateway!")
|
||||
.tap_err(|_| log::warn!("Failed to register with the gateway!"))?;
|
||||
Ok(shared_keys)
|
||||
}
|
||||
|
||||
pub fn show_address<T>(config: &Config<T>)
|
||||
pub fn show_address<T>(config: &Config<T>) -> Result<(), ClientCoreError>
|
||||
where
|
||||
T: config::NymConfig,
|
||||
{
|
||||
fn load_identity_keys(pathfinder: &ClientKeyPathfinder) -> identity::KeyPair {
|
||||
fn load_identity_keys(
|
||||
pathfinder: &ClientKeyPathfinder,
|
||||
) -> Result<identity::KeyPair, ClientCoreError> {
|
||||
let identity_keypair: identity::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_identity_key().to_owned(),
|
||||
pathfinder.public_identity_key().to_owned(),
|
||||
))
|
||||
.expect("Failed to read stored identity key files");
|
||||
identity_keypair
|
||||
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
|
||||
Ok(identity_keypair)
|
||||
}
|
||||
|
||||
fn load_sphinx_keys(pathfinder: &ClientKeyPathfinder) -> encryption::KeyPair {
|
||||
fn load_sphinx_keys(
|
||||
pathfinder: &ClientKeyPathfinder,
|
||||
) -> Result<encryption::KeyPair, ClientCoreError> {
|
||||
let sphinx_keypair: encryption::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_encryption_key().to_owned(),
|
||||
pathfinder.public_encryption_key().to_owned(),
|
||||
))
|
||||
.expect("Failed to read stored sphinx key files");
|
||||
sphinx_keypair
|
||||
.tap_err(|_| log::error!("Failed to read stored sphinx key files"))?;
|
||||
Ok(sphinx_keypair)
|
||||
}
|
||||
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config);
|
||||
let identity_keypair = load_identity_keys(&pathfinder);
|
||||
let sphinx_keypair = load_sphinx_keys(&pathfinder);
|
||||
let identity_keypair = load_identity_keys(&pathfinder)?;
|
||||
let sphinx_keypair = load_sphinx_keys(&pathfinder)?;
|
||||
|
||||
let client_recipient = Recipient::new(
|
||||
*identity_keypair.public_key(),
|
||||
*sphinx_keypair.public_key(),
|
||||
// TODO: below only works under assumption that gateway address == gateway id
|
||||
// (which currently is true)
|
||||
NodeIdentity::from_base58_string(config.get_gateway_id()).unwrap(),
|
||||
NodeIdentity::from_base58_string(config.get_gateway_id())?,
|
||||
);
|
||||
|
||||
println!("\nThe address of this client is: {}", client_recipient);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod init;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Args;
|
||||
use client_core::config::GatewayEndpoint;
|
||||
use client_core::{config::GatewayEndpoint, error::ClientCoreError};
|
||||
use config::NymConfig;
|
||||
|
||||
use crate::{
|
||||
@@ -120,7 +120,12 @@ pub(crate) async fn execute(args: &Init) {
|
||||
let override_config_fields = OverrideConfig::from(args.clone());
|
||||
config = override_config(config, override_config_fields);
|
||||
|
||||
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config).await;
|
||||
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config)
|
||||
.await
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Failed to setup gateway\nError: {err}");
|
||||
std::process::exit(1)
|
||||
});
|
||||
config.get_base_mut().with_gateway_endpoint(gateway);
|
||||
|
||||
let config_save_location = config.get_config_file_save_location();
|
||||
@@ -138,7 +143,10 @@ pub(crate) async fn execute(args: &Init) {
|
||||
);
|
||||
println!("Client configuration completed.");
|
||||
|
||||
client_core::init::show_address(config.get_base());
|
||||
client_core::init::show_address(config.get_base()).unwrap_or_else(|err| {
|
||||
eprintln!("Failed to show address\nError: {err}");
|
||||
std::process::exit(1)
|
||||
});
|
||||
}
|
||||
|
||||
async fn setup_gateway(
|
||||
@@ -146,7 +154,7 @@ async fn setup_gateway(
|
||||
register: bool,
|
||||
user_chosen_gateway_id: Option<&str>,
|
||||
config: &Config,
|
||||
) -> GatewayEndpoint {
|
||||
) -> Result<GatewayEndpoint, ClientCoreError> {
|
||||
if register {
|
||||
// Get the gateway details by querying the validator-api. Either pick one at random or use
|
||||
// the chosen one if it's among the available ones.
|
||||
@@ -155,16 +163,16 @@ async fn setup_gateway(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
user_chosen_gateway_id,
|
||||
)
|
||||
.await;
|
||||
.await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
|
||||
// Registering with gateway by setting up and writing shared keys to disk
|
||||
log::trace!("Registering gateway");
|
||||
client_core::init::register_with_gateway_and_store_keys(gateway.clone(), config.get_base())
|
||||
.await;
|
||||
.await?;
|
||||
println!("Saved all generated keys");
|
||||
|
||||
gateway.into()
|
||||
Ok(gateway.into())
|
||||
} else if user_chosen_gateway_id.is_some() {
|
||||
// Just set the config, don't register or create any keys
|
||||
// This assumes that the user knows what they are doing, and that the existing keys are
|
||||
@@ -174,22 +182,22 @@ async fn setup_gateway(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
user_chosen_gateway_id,
|
||||
)
|
||||
.await;
|
||||
.await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
gateway.into()
|
||||
Ok(gateway.into())
|
||||
} else {
|
||||
println!("Not registering gateway, will reuse existing config and keys");
|
||||
match Config::load_from_file(Some(id)) {
|
||||
Ok(existing_config) => existing_config.get_base().get_gateway_endpoint().clone(),
|
||||
Err(err) => {
|
||||
panic!(
|
||||
"Unable to configure gateway: {err}. \n
|
||||
Seems like the client was already initialized but it was not possible to read \
|
||||
the existing configuration file. \n
|
||||
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
|
||||
removing the existing configuration and starting over."
|
||||
)
|
||||
}
|
||||
}
|
||||
let existing_config = Config::load_from_file(Some(id)).map_err(|err| {
|
||||
log::error!(
|
||||
"Unable to configure gateway: {err}. \n
|
||||
Seems like the client was already initialized but it was not possible to read \
|
||||
the existing configuration file. \n
|
||||
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
|
||||
removing the existing configuration and starting over."
|
||||
);
|
||||
ClientCoreError::CouldNotLoadExistingGatewayConfiguration(err)
|
||||
})?;
|
||||
|
||||
Ok(existing_config.get_base().get_gateway_endpoint().clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Args;
|
||||
use client_core::config::GatewayEndpoint;
|
||||
use client_core::{config::GatewayEndpoint, error::ClientCoreError};
|
||||
use config::NymConfig;
|
||||
|
||||
use crate::{
|
||||
@@ -120,7 +120,12 @@ pub(crate) async fn execute(args: &Init) {
|
||||
let override_config_fields = OverrideConfig::from(args.clone());
|
||||
config = override_config(config, override_config_fields);
|
||||
|
||||
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config).await;
|
||||
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config)
|
||||
.await
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Failed to setup gateway\nError: {err}");
|
||||
std::process::exit(1)
|
||||
});
|
||||
config.get_base_mut().with_gateway_endpoint(gateway);
|
||||
|
||||
let config_save_location = config.get_config_file_save_location();
|
||||
@@ -138,7 +143,10 @@ pub(crate) async fn execute(args: &Init) {
|
||||
);
|
||||
println!("Client configuration completed.");
|
||||
|
||||
client_core::init::show_address(config.get_base());
|
||||
client_core::init::show_address(config.get_base()).unwrap_or_else(|err| {
|
||||
eprintln!("Failed to show address\nError: {err}");
|
||||
std::process::exit(1)
|
||||
});
|
||||
}
|
||||
|
||||
async fn setup_gateway(
|
||||
@@ -146,7 +154,7 @@ async fn setup_gateway(
|
||||
register: bool,
|
||||
user_chosen_gateway_id: Option<&str>,
|
||||
config: &Config,
|
||||
) -> GatewayEndpoint {
|
||||
) -> Result<GatewayEndpoint, ClientCoreError> {
|
||||
if register {
|
||||
// Get the gateway details by querying the validator-api. Either pick one at random or use
|
||||
// the chosen one if it's among the available ones.
|
||||
@@ -155,16 +163,16 @@ async fn setup_gateway(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
user_chosen_gateway_id,
|
||||
)
|
||||
.await;
|
||||
.await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
|
||||
// Registering with gateway by setting up and writing shared keys to disk
|
||||
log::trace!("Registering gateway");
|
||||
client_core::init::register_with_gateway_and_store_keys(gateway.clone(), config.get_base())
|
||||
.await;
|
||||
.await?;
|
||||
println!("Saved all generated keys");
|
||||
|
||||
gateway.into()
|
||||
Ok(gateway.into())
|
||||
} else if user_chosen_gateway_id.is_some() {
|
||||
// Just set the config, don't register or create any keys
|
||||
// This assumes that the user knows what they are doing, and that the existing keys are
|
||||
@@ -174,22 +182,21 @@ async fn setup_gateway(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
user_chosen_gateway_id,
|
||||
)
|
||||
.await;
|
||||
.await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
gateway.into()
|
||||
Ok(gateway.into())
|
||||
} else {
|
||||
println!("Not registering gateway, will reuse existing config and keys");
|
||||
match Config::load_from_file(Some(id)) {
|
||||
Ok(existing_config) => existing_config.get_base().get_gateway_endpoint().clone(),
|
||||
Err(err) => {
|
||||
panic!(
|
||||
"Unable to configure gateway: {err}. \n
|
||||
Seems like the client was already initialized but it was not possible to read \
|
||||
the existing configuration file. \n
|
||||
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
|
||||
removing the existing configuration and starting over."
|
||||
)
|
||||
}
|
||||
}
|
||||
let existing_config = Config::load_from_file(Some(id)).map_err(|err| {
|
||||
log::error!(
|
||||
"Unable to configure gateway: {err}. \n
|
||||
Seems like the client was already initialized but it was not possible to read \
|
||||
the existing configuration file. \n
|
||||
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
|
||||
removing the existing configuration and starting over."
|
||||
);
|
||||
ClientCoreError::CouldNotLoadExistingGatewayConfiguration(err)
|
||||
})?;
|
||||
Ok(existing_config.get_base().get_gateway_endpoint().clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,7 +489,7 @@ impl TryFrom<ProtoContractCodeHistoryEntry> for ContractCodeHistoryEntry {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize)]
|
||||
pub struct GasInfo {
|
||||
/// GasWanted is the maximum units of work we allow this tx to perform.
|
||||
pub gas_wanted: Gas,
|
||||
@@ -645,7 +645,7 @@ impl InstantiateOptions {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct InstantiateResult {
|
||||
/// The address of the newly instantiated contract
|
||||
pub contract_address: AccountId,
|
||||
@@ -658,7 +658,7 @@ pub struct InstantiateResult {
|
||||
pub gas_info: GasInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ChangeAdminResult {
|
||||
pub logs: Vec<Log>,
|
||||
|
||||
@@ -668,7 +668,7 @@ pub struct ChangeAdminResult {
|
||||
pub gas_info: GasInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct MigrateResult {
|
||||
pub logs: Vec<Log>,
|
||||
|
||||
@@ -678,7 +678,7 @@ pub struct MigrateResult {
|
||||
pub gas_info: GasInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ExecuteResult {
|
||||
pub logs: Vec<Log>,
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ use crate::nymd::error::NymdError;
|
||||
use crate::nymd::fee::DEFAULT_SIMULATED_GAS_MULTIPLIER;
|
||||
use crate::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
use cosmrs::cosmwasm;
|
||||
use cosmrs::rpc::endpoint::block::Response as BlockResponse;
|
||||
use cosmrs::rpc::query::Query;
|
||||
use cosmrs::rpc::Error as TendermintRpcError;
|
||||
use cosmrs::rpc::HttpClientUrl;
|
||||
use cosmrs::tx::Msg;
|
||||
@@ -212,6 +214,10 @@ impl<C> NymdClient<C> {
|
||||
&self.config
|
||||
}
|
||||
|
||||
pub fn current_chain_details(&self) -> &ChainDetails {
|
||||
&self.config.chain_details
|
||||
}
|
||||
|
||||
pub fn set_mixnet_contract_address(&mut self, address: AccountId) {
|
||||
self.config.mixnet_contract_address = Some(address);
|
||||
}
|
||||
@@ -355,11 +361,26 @@ impl<C> NymdClient<C> {
|
||||
address: &AccountId,
|
||||
) -> Result<Option<Account>, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
self.client.get_account(address).await
|
||||
}
|
||||
|
||||
pub async fn get_account_public_key(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
) -> Result<Option<cosmrs::crypto::PublicKey>, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
if let Some(account) = self.client.get_account(address).await? {
|
||||
let base_account = account.try_get_base_account()?;
|
||||
return Ok(base_account.pubkey);
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub async fn get_current_block_timestamp(&self) -> Result<TendermintTime, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
@@ -377,6 +398,13 @@ impl<C> NymdClient<C> {
|
||||
Ok(self.client.get_block(height).await?.block.header.time)
|
||||
}
|
||||
|
||||
pub async fn get_block(&self, height: Option<u32>) -> Result<BlockResponse, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
self.client.get_block(height).await
|
||||
}
|
||||
|
||||
pub async fn get_current_block_height(&self) -> Result<Height, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
@@ -421,6 +449,13 @@ impl<C> NymdClient<C> {
|
||||
self.client.get_balance(address, denom).await
|
||||
}
|
||||
|
||||
pub async fn get_all_balances(&self, address: &AccountId) -> Result<Vec<Coin>, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
self.client.get_all_balances(address).await
|
||||
}
|
||||
|
||||
pub async fn get_tx(&self, id: tx::Hash) -> Result<TxResponse, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
@@ -428,6 +463,13 @@ impl<C> NymdClient<C> {
|
||||
self.client.get_tx(id).await
|
||||
}
|
||||
|
||||
pub async fn search_tx(&self, query: Query) -> Result<Vec<TxResponse>, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
{
|
||||
self.client.search_tx(query).await
|
||||
}
|
||||
|
||||
pub async fn get_total_supply(&self) -> Result<Vec<Coin>, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::nymd::error::NymdError;
|
||||
use config::defaults;
|
||||
use cosmrs::bip32::{DerivationPath, XPrv};
|
||||
use cosmrs::crypto::secp256k1::SigningKey;
|
||||
use cosmrs::crypto::secp256k1::{Signature, SigningKey};
|
||||
use cosmrs::crypto::PublicKey;
|
||||
use cosmrs::tx::SignDoc;
|
||||
use cosmrs::{tx, AccountId};
|
||||
@@ -105,6 +105,17 @@ impl DirectSecp256k1HdWallet {
|
||||
self.secret.to_string()
|
||||
}
|
||||
|
||||
pub fn sign_raw_with_account(
|
||||
&self,
|
||||
signer: &AccountData,
|
||||
message: &[u8],
|
||||
) -> Result<Signature, NymdError> {
|
||||
signer
|
||||
.private_key
|
||||
.sign(message)
|
||||
.map_err(|_| NymdError::SigningFailure)
|
||||
}
|
||||
|
||||
pub fn sign_direct_with_account(
|
||||
&self,
|
||||
signer: &AccountData,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "nym-cli-commands"
|
||||
version = "1.0.0"
|
||||
authors = ["Nym Technologies SA"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.13.0"
|
||||
bip39 = "1.0.1"
|
||||
bs58 = "0.4"
|
||||
comfy-table = "6.0.0"
|
||||
cfg-if = "1.0.0"
|
||||
clap = { version = "3.2", features = ["derive"] }
|
||||
handlebars = "3.0.1"
|
||||
humantime-serde = "1.0"
|
||||
k256 = { version = "0.10", features = ["ecdsa", "sha256"] }
|
||||
log = "0.4"
|
||||
rand = {version = "0.6", features = ["std"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
thiserror = "1"
|
||||
time = { version = "0.3.6", features = ["parsing", "formatting"] }
|
||||
toml = "0.5.6"
|
||||
url = "2.2"
|
||||
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
cosmwasm-std = { version = "1.0.0" }
|
||||
|
||||
validator-client = { path = "../client-libs/validator-client", features = ["nymd-client"] }
|
||||
network-defaults = { path = "../network-defaults" }
|
||||
mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
vesting-contract-common = { path = "../cosmwasm-smart-contracts/vesting-contract" }
|
||||
@@ -0,0 +1,13 @@
|
||||
# Common `clap` Command Crate
|
||||
|
||||
This crate contains `clap` commands for common operations:
|
||||
|
||||
- account creation and queries
|
||||
- block queries
|
||||
- cosmwasm uploads, instantiate, execution, query, etc
|
||||
- mixnet actions and queries
|
||||
- sign and verify messages
|
||||
- query for transactions
|
||||
- create vesting schedules and query for them
|
||||
|
||||
For how to use this crate, please see the [Nym CLI](../../tools/nym-cli).
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// TODO: add coconut commands here
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ContextError {
|
||||
#[error("mnemonic was not provided, pass as an argument or an env var called MNEMONIC")]
|
||||
MnemonicNotProvided,
|
||||
|
||||
#[error("failed to parse mnemonic - {0}")]
|
||||
Bip39Error(#[from] bip39::Error),
|
||||
|
||||
// there are lots of error that can occur in the nymd client, so just pass through their display details
|
||||
// TODO: improve this to return known errors
|
||||
#[error("failed to create client - {0}")]
|
||||
NymdError(String),
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use network_defaults::{
|
||||
setup_env,
|
||||
var_names::{API_VALIDATOR, MIXNET_CONTRACT_ADDRESS, NYMD_VALIDATOR, VESTING_CONTRACT_ADDRESS},
|
||||
NymNetworkDetails,
|
||||
};
|
||||
use validator_client::nymd::{self, AccountId, NymdClient, QueryNymdClient, SigningNymdClient};
|
||||
pub use validator_client::validator_api::Client as ValidatorApiClient;
|
||||
|
||||
use crate::context::errors::ContextError;
|
||||
|
||||
pub mod errors;
|
||||
|
||||
pub type SigningClient = validator_client::nymd::NymdClient<SigningNymdClient>;
|
||||
pub type QueryClient = validator_client::nymd::NymdClient<QueryNymdClient>;
|
||||
pub type SigningClientWithValidatorAPI = validator_client::Client<SigningNymdClient>;
|
||||
pub type QueryClientWithValidatorAPI = validator_client::Client<QueryNymdClient>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ClientArgs {
|
||||
pub config_env_file: Option<std::path::PathBuf>,
|
||||
pub nymd_url: Option<String>,
|
||||
pub validator_api_url: Option<String>,
|
||||
pub mnemonic: Option<bip39::Mnemonic>,
|
||||
pub mixnet_contract_address: Option<AccountId>,
|
||||
pub vesting_contract_address: Option<AccountId>,
|
||||
}
|
||||
|
||||
pub fn get_network_details(args: &ClientArgs) -> Result<NymNetworkDetails, ContextError> {
|
||||
// let the network defaults crate handle setting up the env vars if the file arg is set, otherwise
|
||||
// it will default to what is already in env vars, falling back to mainnet
|
||||
setup_env(args.config_env_file.clone());
|
||||
|
||||
// override the env vars with user supplied arguments, if set
|
||||
if let Some(nymd_url) = args.nymd_url.as_ref() {
|
||||
std::env::set_var(NYMD_VALIDATOR, nymd_url);
|
||||
}
|
||||
if let Some(validator_api_url) = args.validator_api_url.as_ref() {
|
||||
std::env::set_var(API_VALIDATOR, validator_api_url);
|
||||
}
|
||||
if let Some(mixnet_contract_address) = args.mixnet_contract_address.as_ref() {
|
||||
std::env::set_var(MIXNET_CONTRACT_ADDRESS, mixnet_contract_address.to_string());
|
||||
}
|
||||
if let Some(vesting_contract_address) = args.vesting_contract_address.as_ref() {
|
||||
std::env::set_var(
|
||||
VESTING_CONTRACT_ADDRESS,
|
||||
vesting_contract_address.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(NymNetworkDetails::new_from_env())
|
||||
}
|
||||
|
||||
pub fn create_signing_client(
|
||||
args: ClientArgs,
|
||||
network_details: &NymNetworkDetails,
|
||||
) -> Result<SigningClient, ContextError> {
|
||||
let client_config = nymd::Config::try_from_nym_network_details(network_details)
|
||||
.expect("failed to construct valid validator client config with the provided network");
|
||||
|
||||
// get mnemonic
|
||||
let mnemonic = match std::env::var("MNEMONIC") {
|
||||
Ok(value) => bip39::Mnemonic::parse(value)?,
|
||||
// env var MNEMONIC is not present, so try to fall back to arg --mnemonic ...
|
||||
Err(_) => match args.mnemonic {
|
||||
Some(value) => value,
|
||||
None => return Err(ContextError::MnemonicNotProvided), // no env var or arg provided
|
||||
},
|
||||
};
|
||||
|
||||
let nymd_url = network_details
|
||||
.endpoints
|
||||
.first()
|
||||
.expect("network details are not defined")
|
||||
.nymd_url
|
||||
.as_str();
|
||||
|
||||
match NymdClient::connect_with_mnemonic(client_config, nymd_url, mnemonic, None) {
|
||||
Ok(client) => Ok(client),
|
||||
Err(e) => Err(ContextError::NymdError(format!("{:?}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_query_client(
|
||||
network_details: &NymNetworkDetails,
|
||||
) -> Result<QueryClient, ContextError> {
|
||||
let client_config = nymd::Config::try_from_nym_network_details(network_details)
|
||||
.expect("failed to construct valid validator client config with the provided network");
|
||||
|
||||
let nymd_url = network_details
|
||||
.endpoints
|
||||
.first()
|
||||
.expect("network details are not defined")
|
||||
.nymd_url
|
||||
.as_str();
|
||||
|
||||
match NymdClient::connect(client_config, nymd_url) {
|
||||
Ok(client) => Ok(client),
|
||||
Err(e) => Err(ContextError::NymdError(format!("{:?}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_signing_client_with_validator_api(
|
||||
args: ClientArgs,
|
||||
network_details: &NymNetworkDetails,
|
||||
) -> Result<SigningClientWithValidatorAPI, ContextError> {
|
||||
let client_config = validator_client::Config::try_from_nym_network_details(network_details)
|
||||
.expect("failed to construct valid validator client config with the provided network");
|
||||
|
||||
// get mnemonic
|
||||
let mnemonic = match std::env::var("MNEMONIC") {
|
||||
Ok(value) => bip39::Mnemonic::parse(value)?,
|
||||
// env var MNEMONIC is not present, so try to fall back to arg --mnemonic ...
|
||||
Err(_) => match args.mnemonic {
|
||||
Some(value) => value,
|
||||
None => return Err(ContextError::MnemonicNotProvided), // no env var or arg provided
|
||||
},
|
||||
};
|
||||
|
||||
match validator_client::client::Client::new_signing(client_config, mnemonic) {
|
||||
Ok(client) => Ok(client),
|
||||
Err(e) => Err(ContextError::NymdError(format!("{:?}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_query_client_with_validator_api(
|
||||
network_details: &NymNetworkDetails,
|
||||
) -> Result<QueryClientWithValidatorAPI, ContextError> {
|
||||
let client_config = validator_client::Config::try_from_nym_network_details(network_details)
|
||||
.expect("failed to construct valid validator client config with the provided network");
|
||||
|
||||
match validator_client::client::Client::new_query(client_config) {
|
||||
Ok(client) => Ok(client),
|
||||
Err(e) => Err(ContextError::NymdError(format!("{:?}", e))),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod coconut;
|
||||
pub mod context;
|
||||
pub mod utils;
|
||||
pub mod validator;
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt::Display;
|
||||
|
||||
use cosmwasm_std::{Coin as CosmWasmCoin, Decimal};
|
||||
use log::error;
|
||||
use validator_client::nymd::Coin;
|
||||
|
||||
pub fn pretty_coin(coin: &Coin) -> String {
|
||||
let amount = Decimal::from_ratio(coin.amount, 1_000_000u128);
|
||||
let denom = if coin.denom.starts_with('u') {
|
||||
&coin.denom[1..]
|
||||
} else {
|
||||
&coin.denom
|
||||
};
|
||||
format!("{} {}", amount, denom)
|
||||
}
|
||||
|
||||
pub fn pretty_cosmwasm_coin(coin: &CosmWasmCoin) -> String {
|
||||
let amount = Decimal::from_ratio(coin.amount, 1_000_000u128);
|
||||
let denom = if coin.denom.starts_with('u') {
|
||||
&coin.denom[1..]
|
||||
} else {
|
||||
&coin.denom
|
||||
};
|
||||
format!("{} {}", amount, denom)
|
||||
}
|
||||
|
||||
pub fn show_error<E>(e: E)
|
||||
where
|
||||
E: Display,
|
||||
{
|
||||
error!("{}", e);
|
||||
}
|
||||
|
||||
pub fn show_error_passthrough<E>(e: E) -> E
|
||||
where
|
||||
E: Error + Display,
|
||||
{
|
||||
error!("{}", e);
|
||||
e
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use log::{error, info};
|
||||
|
||||
use validator_client::nymd::AccountId;
|
||||
|
||||
use crate::context::QueryClient;
|
||||
use crate::utils::{pretty_coin, show_error};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser)]
|
||||
#[clap(help = "The account address to get the balance for")]
|
||||
pub address: Option<AccountId>,
|
||||
|
||||
#[clap(long)]
|
||||
#[clap(help = "Optional currency to show balance for")]
|
||||
pub denom: Option<String>,
|
||||
|
||||
#[clap(long, requires = "denom")]
|
||||
#[clap(help = "Optionally hide the denom")]
|
||||
pub hide_denom: bool,
|
||||
|
||||
#[clap(long)]
|
||||
#[clap(help = "Show as a raw value")]
|
||||
pub raw: bool,
|
||||
}
|
||||
|
||||
pub async fn query_balance(
|
||||
args: Args,
|
||||
client: &QueryClient,
|
||||
address_from_mnemonic: Option<AccountId>,
|
||||
) {
|
||||
if args.address.is_none() && address_from_mnemonic.is_none() {
|
||||
error!("Please specify an account address or a mnemonic to get the balance for");
|
||||
return;
|
||||
}
|
||||
|
||||
let address = args
|
||||
.address
|
||||
.unwrap_or_else(|| address_from_mnemonic.expect("please provide a mnemonic"));
|
||||
|
||||
info!("Getting balance for {}...", address);
|
||||
|
||||
match client.get_all_balances(&address).await {
|
||||
Ok(coins) => {
|
||||
if coins.is_empty() {
|
||||
println!("No balance");
|
||||
return;
|
||||
}
|
||||
|
||||
let denom = args.denom.unwrap_or_else(|| "".to_string());
|
||||
|
||||
for coin in coins {
|
||||
if denom.is_empty() || denom.eq_ignore_ascii_case(&coin.denom) {
|
||||
if args.raw {
|
||||
if !args.hide_denom {
|
||||
println!("{}", coin);
|
||||
} else {
|
||||
println!("{}", coin.amount);
|
||||
}
|
||||
} else {
|
||||
println!("{}", pretty_coin(&coin));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => show_error(e),
|
||||
}
|
||||
}
|
||||
@@ -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 struct Args {
|
||||
#[clap(long)]
|
||||
// allowed values are 12, 18 or 24
|
||||
pub word_count: Option<usize>,
|
||||
}
|
||||
|
||||
pub 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,28 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod balance;
|
||||
pub mod create;
|
||||
pub mod pubkey;
|
||||
pub mod send;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct Account {
|
||||
#[clap(subcommand)]
|
||||
pub command: Option<AccountCommands>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum AccountCommands {
|
||||
/// Create a new mnemonic - note, this account does not appear on the chain until the account id is used in a transaction
|
||||
Create(crate::validator::account::create::Args),
|
||||
/// Gets the balance of an account
|
||||
Balance(crate::validator::account::balance::Args),
|
||||
/// Gets the public key of an account
|
||||
PubKey(crate::validator::account::pubkey::Args),
|
||||
/// Sends tokens to another account
|
||||
Send(crate::validator::account::send::Args),
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use log::{error, info};
|
||||
|
||||
use validator_client::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
use validator_client::nymd::AccountId;
|
||||
|
||||
use crate::context::QueryClient;
|
||||
use crate::utils::show_error;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser)]
|
||||
#[clap(
|
||||
help = "Optionally, show the public key for this account address, otherwise generate the account address from the mnemonic"
|
||||
)]
|
||||
pub address: Option<AccountId>,
|
||||
|
||||
#[clap(long)]
|
||||
#[clap(help = "If set, get the public key from the mnemonic, rather than querying for it")]
|
||||
pub from_mnemonic: bool,
|
||||
}
|
||||
|
||||
pub async fn get_pubkey(
|
||||
args: Args,
|
||||
client: &QueryClient,
|
||||
mnemonic: Option<bip39::Mnemonic>,
|
||||
address_from_mnemonic: Option<AccountId>,
|
||||
) {
|
||||
if args.address.is_none() && address_from_mnemonic.is_none() {
|
||||
error!("Please specify an account address or a mnemonic to get the balance for");
|
||||
return;
|
||||
}
|
||||
|
||||
let address = args
|
||||
.address
|
||||
.unwrap_or_else(|| address_from_mnemonic.expect("please provide a mnemonic"));
|
||||
|
||||
if args.from_mnemonic {
|
||||
let prefix = client
|
||||
.current_chain_details()
|
||||
.bech32_account_prefix
|
||||
.as_str();
|
||||
get_pubkey_from_mnemonic(address, prefix, mnemonic.expect("mnemonic not set"));
|
||||
return;
|
||||
}
|
||||
|
||||
get_pubkey_from_chain(address, client).await;
|
||||
}
|
||||
|
||||
pub fn get_pubkey_from_mnemonic(address: AccountId, prefix: &str, mnemonic: bip39::Mnemonic) {
|
||||
match DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic) {
|
||||
Ok(wallet) => match wallet.try_derive_accounts() {
|
||||
Ok(accounts) => match accounts.iter().find(|a| *a.address() == address) {
|
||||
Some(account) => {
|
||||
println!("{}", account.public_key().to_string());
|
||||
}
|
||||
None => {
|
||||
error!("Could not derive key that matches {}", address)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to derive accounts. {}", e);
|
||||
}
|
||||
},
|
||||
Err(e) => show_error(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_pubkey_from_chain(address: AccountId, client: &QueryClient) {
|
||||
info!("Getting public key for address {} from chain...", address);
|
||||
match client.get_account_details(&address).await {
|
||||
Ok(Some(account)) => {
|
||||
if let Ok(base_account) = account.try_get_base_account() {
|
||||
if let Some(pubkey) = base_account.pubkey {
|
||||
println!("{}", pubkey.to_string());
|
||||
} else {
|
||||
println!("No account associated with address {}", address);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
println!("No account associated with address {}", address);
|
||||
}
|
||||
Err(e) => show_error(e),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use serde_json::json;
|
||||
|
||||
use validator_client::nymd::{AccountId, Coin};
|
||||
|
||||
use crate::context::SigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser, help = "The recipient account address")]
|
||||
pub recipient: AccountId,
|
||||
|
||||
#[clap(
|
||||
value_parser,
|
||||
help = "Amount to transfer in micro denomination (e.g. unym or unyx)"
|
||||
)]
|
||||
pub amount: u128,
|
||||
|
||||
#[clap(long, help = "Override the denomination")]
|
||||
pub denom: Option<String>,
|
||||
|
||||
#[clap(long)]
|
||||
pub memo: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn send(args: Args, client: &SigningClient) {
|
||||
let memo = args
|
||||
.memo
|
||||
.unwrap_or_else(|| "Sending tokens with nym-cli".to_owned());
|
||||
let denom = args
|
||||
.denom
|
||||
.unwrap_or_else(|| client.current_chain_details().mix_denom.base.clone());
|
||||
|
||||
let coin = Coin {
|
||||
denom,
|
||||
amount: args.amount,
|
||||
};
|
||||
|
||||
info!(
|
||||
"Sending {} {} from {} to {}...",
|
||||
coin.amount,
|
||||
coin.denom,
|
||||
client.address(),
|
||||
args.recipient
|
||||
);
|
||||
|
||||
let res = client
|
||||
.send(&args.recipient, vec![coin], memo, None)
|
||||
.await
|
||||
.expect("failed to send tokens!");
|
||||
|
||||
info!("Sending result: {}", json!(res));
|
||||
|
||||
println!();
|
||||
println!(
|
||||
"Nodesguru: https://nym.explorers.guru/transaction/{}",
|
||||
&res.hash
|
||||
);
|
||||
println!("Mintscan: https://www.mintscan.io/nyx/txs/{}", &res.hash);
|
||||
println!("Transaction result code: {}", &res.tx_result.code.value());
|
||||
println!("Transaction hash: {}", &res.hash);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::context::QueryClient;
|
||||
use crate::utils::show_error;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser)]
|
||||
#[clap(help = "The block height")]
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
pub async fn query_for_block_time(args: Args, client: &QueryClient) {
|
||||
match client.get_block_timestamp(Some(args.height)).await {
|
||||
Ok(res) => {
|
||||
println!("{}", res.to_rfc3339())
|
||||
}
|
||||
Err(e) => show_error(e),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::context::QueryClient;
|
||||
use crate::utils::show_error;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {}
|
||||
|
||||
pub async fn query_current_block_height(client: &QueryClient) {
|
||||
match client.get_current_block_height().await {
|
||||
Ok(res) => {
|
||||
println!("Current block height:\n{}", res.value())
|
||||
}
|
||||
Err(e) => show_error(e),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::context::QueryClient;
|
||||
use crate::utils::show_error;
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser)]
|
||||
#[clap(help = "The block height")]
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
pub async fn query_for_block(args: Args, client: &QueryClient) {
|
||||
match client.get_block(Some(args.height)).await {
|
||||
Ok(res) => {
|
||||
println!("{}", json!(res))
|
||||
}
|
||||
Err(e) => show_error(e),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod block_time;
|
||||
pub mod current_height;
|
||||
pub mod get;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct Block {
|
||||
#[clap(subcommand)]
|
||||
pub command: Option<BlockCommands>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum BlockCommands {
|
||||
/// Gets a block's details and prints as JSON
|
||||
Get(crate::validator::block::get::Args),
|
||||
/// Gets the block time at a height
|
||||
Time(crate::validator::block::block_time::Args),
|
||||
/// Gets the current block height
|
||||
CurrentHeight(crate::validator::block::current_height::Args),
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use cosmrs::AccountId;
|
||||
use log::{error, info};
|
||||
use serde_json::{json, Value};
|
||||
use validator_client::nymd::Coin;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser)]
|
||||
#[clap(help = "The address of contract to execute")]
|
||||
pub contract_address: AccountId,
|
||||
|
||||
#[clap(value_parser)]
|
||||
#[clap(help = "JSON encoded method arguments")]
|
||||
pub json_args: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub memo: Option<String>,
|
||||
|
||||
#[clap(
|
||||
value_parser,
|
||||
requires = "fundsDenom",
|
||||
help = "Amount to supply as funds in micro denomination (e.g. unym or unyx)"
|
||||
)]
|
||||
pub funds: Option<u128>,
|
||||
|
||||
#[clap(long, requires = "funds", help = "Set the denomination for the funds")]
|
||||
pub funds_denom: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn execute(args: Args, client: SigningClient) {
|
||||
info!("Starting contract method execution!");
|
||||
|
||||
let json_args: Value =
|
||||
serde_json::from_str(&args.json_args).expect("Unable to parse JSON args");
|
||||
|
||||
let memo = args
|
||||
.memo
|
||||
.unwrap_or_else(|| "nym-cli execute contract method".to_owned());
|
||||
|
||||
let funds = match args.funds {
|
||||
Some(funds) => vec![Coin::new(
|
||||
funds,
|
||||
args.funds_denom.expect("denom for funds not set"),
|
||||
)],
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
match client
|
||||
.execute(&args.contract_address, &json_args, None, memo, funds)
|
||||
.await
|
||||
{
|
||||
Ok(res) => info!("SUCCESS ✅\n{}", json!(res)),
|
||||
Err(e) => error!("FAILURE ❌\n{}", e),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use cosmrs::{AccountId, Coin as CosmosCoin};
|
||||
use log::info;
|
||||
use network_defaults::NymNetworkDetails;
|
||||
use validator_client::nymd::cosmwasm_client::types::{ContractCodeId, InstantiateOptions};
|
||||
use validator_client::nymd::Coin;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser)]
|
||||
pub code_id: ContractCodeId,
|
||||
|
||||
#[clap(long)]
|
||||
pub memo: Option<String>,
|
||||
|
||||
#[clap(long)]
|
||||
pub label: Option<String>,
|
||||
|
||||
#[clap(long)]
|
||||
pub init_message: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub admin: Option<AccountId>,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
requires = "fundsDenom",
|
||||
help = "Amount to supply as funds in micro denomination (e.g. unym or unyx)"
|
||||
)]
|
||||
pub funds: Option<u128>,
|
||||
|
||||
#[clap(long, requires = "funds", help = "Set the denomination for the funds")]
|
||||
pub funds_denom: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn init(args: Args, client: SigningClient, network_details: &NymNetworkDetails) {
|
||||
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());
|
||||
|
||||
let funds: Vec<CosmosCoin> = match args.funds {
|
||||
Some(funds) => vec![Coin::new(
|
||||
funds,
|
||||
args.funds_denom
|
||||
.unwrap_or_else(|| network_details.chain_details.mix_denom.base.to_string()),
|
||||
)
|
||||
.into()],
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
// by default we make ourselves an admin, let me know if you don't like that behaviour
|
||||
let opts = Some(InstantiateOptions {
|
||||
funds,
|
||||
admin: Some(args.admin.unwrap_or_else(|| client.address().clone())),
|
||||
});
|
||||
|
||||
let msg: serde_json::Value =
|
||||
serde_json::from_str(&args.init_message).expect("failed to parse init message");
|
||||
|
||||
// the EmptyMsg{} argument is equivalent to `--init-message='{}'`
|
||||
let res = client
|
||||
.instantiate(args.code_id, &msg, label, memo, opts, None)
|
||||
.await
|
||||
.expect("failed to instantiate the contract!");
|
||||
|
||||
info!("Init result: {:?}", res);
|
||||
|
||||
println!("{}", res.contract_address)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use crate::utils::show_error_passthrough;
|
||||
use clap::Parser;
|
||||
use cosmrs::AccountId;
|
||||
use log::info;
|
||||
use validator_client::nymd::cosmwasm_client::types::{ContractCodeId, EmptyMsg};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser)]
|
||||
pub contract_address: AccountId,
|
||||
|
||||
#[clap(long)]
|
||||
pub code_id: ContractCodeId,
|
||||
|
||||
#[clap(long)]
|
||||
pub memo: Option<String>,
|
||||
|
||||
#[clap(long)]
|
||||
pub init_message: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn migrate(args: Args, client: SigningClient) {
|
||||
println!("Starting contract migration!");
|
||||
|
||||
let memo = args.memo.unwrap_or_else(|| "contract migration".to_owned());
|
||||
let contract_address = args.contract_address;
|
||||
|
||||
// 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
|
||||
.migrate(&contract_address, args.code_id, &msg, memo, None)
|
||||
.await
|
||||
.map_err(show_error_passthrough)
|
||||
.expect("failed to migrate the contract!")
|
||||
} else {
|
||||
client
|
||||
.migrate(&contract_address, args.code_id, &EmptyMsg {}, memo, None)
|
||||
.await
|
||||
.map_err(show_error_passthrough)
|
||||
.expect("failed to migrate the contract!")
|
||||
};
|
||||
|
||||
info!("Migrate result: {:?}", res);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod execute_contract;
|
||||
pub mod init_contract;
|
||||
pub mod migrate_contract;
|
||||
pub mod upload_contract;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct Cosmwasm {
|
||||
#[clap(subcommand)]
|
||||
pub command: Option<CosmwasmCommands>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum CosmwasmCommands {
|
||||
/// Upload a smart contract WASM blob
|
||||
Upload(crate::validator::cosmwasm::upload_contract::Args),
|
||||
/// Init a WASM smart contract
|
||||
Init(crate::validator::cosmwasm::init_contract::Args),
|
||||
/// Migrate a WASM smart contract
|
||||
Migrate(crate::validator::cosmwasm::migrate_contract::Args),
|
||||
/// Execute a WASM smart contract method
|
||||
Execute(crate::validator::cosmwasm::execute_contract::Args),
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub wasm_path: PathBuf,
|
||||
|
||||
#[clap(long)]
|
||||
pub memo: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn upload(args: Args, client: SigningClient) {
|
||||
info!("Starting contract upload!");
|
||||
|
||||
let mut file = std::fs::File::open(args.wasm_path).expect("failed to open the wasm blob");
|
||||
let mut data = Vec::new();
|
||||
|
||||
file.read_to_end(&mut data).unwrap();
|
||||
|
||||
let memo = args.memo.unwrap_or_else(|| "contract upload".to_owned());
|
||||
|
||||
let res = client
|
||||
.upload(data, memo, None)
|
||||
.await
|
||||
.expect("failed to upload the contract!");
|
||||
|
||||
info!("Upload result: {:?}", res);
|
||||
|
||||
println!("{}", res.code_id)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use mixnet_contract_common::Coin;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub identity_key: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub amount: u128,
|
||||
}
|
||||
|
||||
pub async fn delegate_to_mixnode(args: Args, client: SigningClient) {
|
||||
let denom = client.current_chain_details().mix_denom.base.as_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,35 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod rewards;
|
||||
|
||||
pub mod delegate_to_mixnode;
|
||||
pub mod query_for_delegations;
|
||||
pub mod undelegate_from_mixnode;
|
||||
pub mod vesting_delegate_to_mixnode;
|
||||
pub mod vesting_undelegate_from_mixnode;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct MixnetDelegators {
|
||||
#[clap(subcommand)]
|
||||
pub command: MixnetDelegatorsCommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MixnetDelegatorsCommands {
|
||||
/// Lists current delegations
|
||||
List(query_for_delegations::Args),
|
||||
/// Manage rewards from delegations
|
||||
Rewards(rewards::MixnetDelegatorsReward),
|
||||
/// Delegate to a mixnode
|
||||
Delegate(delegate_to_mixnode::Args),
|
||||
/// Undelegate from a mixnode
|
||||
Undelegate(undelegate_from_mixnode::Args),
|
||||
/// Delegate to a mixnode with locked tokens
|
||||
DelegateVesting(vesting_delegate_to_mixnode::Args),
|
||||
/// Undelegate from a mixnode (when originally using locked tokens)
|
||||
UndelegateVesting(vesting_undelegate_from_mixnode::Args),
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
use crate::context::SigningClientWithValidatorAPI;
|
||||
use crate::utils::{pretty_cosmwasm_coin, show_error_passthrough};
|
||||
|
||||
use comfy_table::Table;
|
||||
use mixnet_contract_common::mixnode::DelegationEvent;
|
||||
use mixnet_contract_common::Delegation;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {}
|
||||
|
||||
pub async fn execute(_args: Args, client: SigningClientWithValidatorAPI) {
|
||||
info!(
|
||||
"Getting delegations for account {}...",
|
||||
client.nymd.address()
|
||||
);
|
||||
|
||||
let delegations = client
|
||||
.get_all_delegator_delegations(client.nymd.address())
|
||||
.await
|
||||
.map_err(show_error_passthrough);
|
||||
|
||||
let mixnet_contract_events = client
|
||||
.nymd
|
||||
.get_pending_delegation_events(client.nymd.address().to_string(), None)
|
||||
.await
|
||||
.map_err(show_error_passthrough);
|
||||
|
||||
let vesting_contract = client.nymd.vesting_contract_address();
|
||||
|
||||
let vesting_contract_events = client
|
||||
.nymd
|
||||
.get_pending_delegation_events(
|
||||
client.nymd.address().to_string(),
|
||||
Some(vesting_contract.to_string()),
|
||||
)
|
||||
.await
|
||||
.map_err(show_error_passthrough);
|
||||
|
||||
if let Ok(res) = delegations {
|
||||
println!();
|
||||
if res.is_empty() {
|
||||
println!("This account has not delegated any tokens to mixnodes");
|
||||
} else {
|
||||
println!("Delegations:");
|
||||
print_delegations(res, &client).await;
|
||||
}
|
||||
}
|
||||
if let Ok(res) = mixnet_contract_events {
|
||||
if !res.is_empty() {
|
||||
println!();
|
||||
println!("Pending delegations (liquid tokens):");
|
||||
print_delegation_events(res, &client).await;
|
||||
}
|
||||
}
|
||||
if let Ok(res) = vesting_contract_events {
|
||||
if !res.is_empty() {
|
||||
println!();
|
||||
println!("Pending delegations (locked tokens):");
|
||||
print_delegation_events(res, &client).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn to_iso_timestamp(block_height: u32, client: &SigningClientWithValidatorAPI) -> String {
|
||||
match client.nymd.get_block_timestamp(Some(block_height)).await {
|
||||
Ok(res) => res.to_rfc3339(),
|
||||
Err(_e) => "-".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn print_delegations(delegations: Vec<Delegation>, client: &SigningClientWithValidatorAPI) {
|
||||
let mut table = Table::new();
|
||||
|
||||
table.set_header(vec!["Timestamp", "Identity Key", "Delegation", "Proxy"]);
|
||||
|
||||
for delegation in delegations {
|
||||
table.add_row(vec![
|
||||
to_iso_timestamp(delegation.block_height as u32, client).await,
|
||||
delegation.node_identity.to_string(),
|
||||
pretty_cosmwasm_coin(&delegation.amount),
|
||||
format!("{:?}", delegation.proxy),
|
||||
]);
|
||||
}
|
||||
|
||||
println!("{table}");
|
||||
}
|
||||
|
||||
async fn print_delegation_events(
|
||||
events: Vec<DelegationEvent>,
|
||||
client: &SigningClientWithValidatorAPI,
|
||||
) {
|
||||
let mut table = Table::new();
|
||||
|
||||
table.set_header(vec![
|
||||
"Timestamp",
|
||||
"Identity Key",
|
||||
"Delegation",
|
||||
"Event Type",
|
||||
]);
|
||||
|
||||
for event in events {
|
||||
match event {
|
||||
DelegationEvent::Delegate(delegation) => {
|
||||
table.add_row(vec![
|
||||
to_iso_timestamp(delegation.block_height as u32, client).await,
|
||||
delegation.node_identity.to_string(),
|
||||
pretty_cosmwasm_coin(&delegation.amount),
|
||||
"Delegate".to_string(),
|
||||
]);
|
||||
}
|
||||
DelegationEvent::Undelegate(undelegate) => {
|
||||
table.add_row(vec![
|
||||
to_iso_timestamp(undelegate.block_height() as u32, client).await,
|
||||
undelegate.mix_identity().to_string(),
|
||||
"-".to_string(),
|
||||
"Undelegate".to_string(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("{table}");
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub identity_key: String,
|
||||
}
|
||||
|
||||
pub async fn claim_delegator_reward(args: Args, client: SigningClient) {
|
||||
info!("Claim delegator reward");
|
||||
|
||||
let res = client
|
||||
.execute_claim_delegator_reward(args.identity_key, None)
|
||||
.await
|
||||
.expect("failed to claim delegator-reward");
|
||||
|
||||
info!("Claiming delegator reward: {:?}", res)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod claim_delegator_reward;
|
||||
pub mod vesting_claim_delegator_reward;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct MixnetDelegatorsReward {
|
||||
#[clap(subcommand)]
|
||||
pub command: MixnetDelegatorsRewardCommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MixnetDelegatorsRewardCommands {
|
||||
/// Claim rewards accumulated during the delegation of unlocked tokens
|
||||
Claim(claim_delegator_reward::Args),
|
||||
/// Claim rewards accumulated during the delegation of locked tokens
|
||||
VestingClaim(vesting_claim_delegator_reward::Args),
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub identity: String,
|
||||
}
|
||||
|
||||
pub async fn vesting_claim_delegator_reward(args: Args, client: SigningClient) {
|
||||
info!("Claim vesting delegator reward");
|
||||
|
||||
let res = client
|
||||
.execute_vesting_claim_delegator_reward(args.identity, None)
|
||||
.await
|
||||
.expect("failed to claim vesting delegator-reward");
|
||||
|
||||
info!("Claiming vesting delegator reward: {:?}", res)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub identity_key: String,
|
||||
}
|
||||
|
||||
pub async fn undelegate_from_mixnode(args: Args, client: SigningClient) {
|
||||
info!("removing stake from mix-node");
|
||||
|
||||
let res = client
|
||||
.remove_mixnode_delegation(&*args.identity_key, None)
|
||||
.await
|
||||
.expect("failed to remove stake from mixnode!");
|
||||
|
||||
info!("removing stake from mixnode: {:?}", res)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
use mixnet_contract_common::Coin;
|
||||
use validator_client::nymd::VestingSigningClient;
|
||||
|
||||
use crate::context::SigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub identity_key: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub amount: u128,
|
||||
}
|
||||
|
||||
pub async fn vesting_delegate_to_mixnode(args: Args, client: SigningClient) {
|
||||
let denom = client.current_chain_details().mix_denom.base.as_str();
|
||||
|
||||
info!("Starting vesting delegation to mixnode");
|
||||
|
||||
let coin = Coin::new(args.amount, denom);
|
||||
|
||||
let res = client
|
||||
.vesting_delegate_to_mixnode(&*args.identity_key, coin.into(), None)
|
||||
.await
|
||||
.expect("failed to delegate to mixnode!");
|
||||
|
||||
info!("vesting delegating to mixnode: {:?}", res);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
use validator_client::nymd::VestingSigningClient;
|
||||
|
||||
use crate::context::SigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub identity_key: String,
|
||||
}
|
||||
|
||||
pub async fn vesting_undelegate_from_mixnode(args: Args, client: SigningClient) {
|
||||
info!("removing stake from vesting mix-node");
|
||||
|
||||
let res = client
|
||||
.vesting_undelegate_from_mixnode(&*args.identity_key, None)
|
||||
.await
|
||||
.expect("failed to remove stake from vesting account on mixnode!");
|
||||
|
||||
info!("removing stake from vesting mixnode: {:?}", res)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod delegators;
|
||||
pub mod operators;
|
||||
pub mod query;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct Mixnet {
|
||||
#[clap(subcommand)]
|
||||
pub command: MixnetCommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MixnetCommands {
|
||||
/// Query the mixnet directory
|
||||
Query(query::MixnetQuery),
|
||||
/// Manage your delegations
|
||||
Delegators(delegators::MixnetDelegators),
|
||||
/// Manage a mixnode or gateway you operate
|
||||
Operators(operators::MixnetOperators),
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
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 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(short, long)]
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
pub async fn bond_gateway(args: Args, client: SigningClient) {
|
||||
let denom = client.current_chain_details().mix_denom.base.as_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,28 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod bond_gateway;
|
||||
pub mod unbond_gateway;
|
||||
pub mod vesting_bond_gateway;
|
||||
pub mod vesting_unbond_gateway;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct MixnetOperatorsGateway {
|
||||
#[clap(subcommand)]
|
||||
pub command: MixnetOperatorsGatewayCommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MixnetOperatorsGatewayCommands {
|
||||
/// Bond to a gateway
|
||||
Bond(bond_gateway::Args),
|
||||
/// Unbound from a gateway
|
||||
Unbound(unbond_gateway::Args),
|
||||
/// Bond to a gateway with locked tokens
|
||||
VestingBond(vesting_bond_gateway::Args),
|
||||
/// Unbound from a gateway (when originally using locked tokens)
|
||||
VestingUnbound(vesting_unbond_gateway::Args),
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {}
|
||||
|
||||
pub async fn unbond_gateway(client: SigningClient) {
|
||||
info!("Starting gateway unbonding!");
|
||||
|
||||
let res = client
|
||||
.unbond_gateway(None)
|
||||
.await
|
||||
.expect("failed to unbond gateway!");
|
||||
|
||||
info!("Unbonding result: {:?}", res)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::{info, warn};
|
||||
use mixnet_contract_common::{Coin, Gateway};
|
||||
use network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
|
||||
use validator_client::nymd::VestingSigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub 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(short, long)]
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
pub async fn vesting_bond_gateway(client: SigningClient, args: Args, denom: &str) {
|
||||
info!("Starting vesting 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 = 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
|
||||
.vesting_bond_gateway(gateway, &*args.signature, coin.into(), None)
|
||||
.await
|
||||
.expect("failed to bond gateway!");
|
||||
|
||||
info!("Vesting bonding gateway result: {:?}", res)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {}
|
||||
|
||||
pub async fn vesting_unbond_gateway(client: SigningClient) {
|
||||
info!("Starting vesting gateway unbonding!");
|
||||
|
||||
let res = client
|
||||
.unbond_gateway(None)
|
||||
.await
|
||||
.expect("failed to unbond vesting gateway!");
|
||||
|
||||
info!("Unbonding vesting result: {:?}", res)
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
use crate::context::SigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub 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(short, long)]
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
pub async fn bond_mixnode(args: Args, client: SigningClient) {
|
||||
let denom = client.current_chain_details().mix_denom.base.as_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,17 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(short, long)]
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
pub 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,19 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod decode_mixnode_key;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct MixnetOperatorsMixnodeKeys {
|
||||
#[clap(subcommand)]
|
||||
pub command: MixnetOperatorsMixnodeKeysCommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MixnetOperatorsMixnodeKeysCommands {
|
||||
/// Decode a mixnode key
|
||||
DecodeMixnodeKey(decode_mixnode_key::Args),
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod bond_mixnode;
|
||||
pub mod keys;
|
||||
pub mod rewards;
|
||||
pub mod settings;
|
||||
pub mod unbond_mixnode;
|
||||
pub mod vesting_bond_mixnode;
|
||||
pub mod vesting_unbond_mixnode;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct MixnetOperatorsMixnode {
|
||||
#[clap(subcommand)]
|
||||
pub command: MixnetOperatorsMixnodeCommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MixnetOperatorsMixnodeCommands {
|
||||
/// Operations for mixnode keys
|
||||
Keys(keys::MixnetOperatorsMixnodeKeys),
|
||||
/// Manage your mixnode operator rewards
|
||||
Rewards(rewards::MixnetOperatorsMixnodeRewards),
|
||||
/// Manage your mixnode settings stored in the directory
|
||||
Settings(settings::MixnetOperatorsMixnodeSettings),
|
||||
/// Bond to a mixnode
|
||||
Bond(bond_mixnode::Args),
|
||||
/// Unbound from a mixnode
|
||||
Unbound(unbond_mixnode::Args),
|
||||
/// Bond to a mixnode with locked tokens
|
||||
BondVesting(vesting_bond_mixnode::Args),
|
||||
/// Unbound from a mixnode (when originally using locked tokens)
|
||||
UnboundVesting(vesting_unbond_mixnode::Args),
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {}
|
||||
|
||||
pub async fn claim_operator_reward(_args: Args, client: SigningClient) {
|
||||
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,22 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod claim_operator_reward;
|
||||
pub mod vesting_claim_operator_reward;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct MixnetOperatorsMixnodeRewards {
|
||||
#[clap(subcommand)]
|
||||
pub command: MixnetOperatorsMixnodeRewardsCommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MixnetOperatorsMixnodeRewardsCommands {
|
||||
/// Claim rewards
|
||||
Claim(claim_operator_reward::Args),
|
||||
/// Claim rewards for a mixnode bonded with locked tokens
|
||||
VestingClaim(vesting_claim_operator_reward::Args),
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub gas: Option<u64>,
|
||||
}
|
||||
|
||||
pub async fn vesting_claim_operator_reward(client: SigningClient) {
|
||||
info!("Claim vesting operator reward");
|
||||
|
||||
let res = client
|
||||
.execute_vesting_claim_operator_reward(None)
|
||||
.await
|
||||
.expect("failed to claim vesting operator reward");
|
||||
|
||||
info!("Claiming vesting operator reward: {:?}", res)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod update_profit_percent;
|
||||
pub mod vesting_update_profit_percent;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct MixnetOperatorsMixnodeSettings {
|
||||
#[clap(subcommand)]
|
||||
pub command: MixnetOperatorsMixnodeSettingsCommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MixnetOperatorsMixnodeSettingsCommands {
|
||||
/// Update profit percentage
|
||||
UpdateProfitPercentage(update_profit_percent::Args),
|
||||
/// Update profit percentage for a mixnode bonded with locked tokens
|
||||
VestingUpdateProfitPercentage(vesting_update_profit_percent::Args),
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub profit_percent: u8,
|
||||
}
|
||||
|
||||
pub async fn update_profit_percent(args: Args, client: SigningClient) {
|
||||
info!("Update mix node profit percent - get those rewards!");
|
||||
|
||||
//profit percent between 1-100
|
||||
let res = client
|
||||
.update_mixnode_config(args.profit_percent, None)
|
||||
.await
|
||||
.expect("updating mix-node profit percent");
|
||||
|
||||
info!("profit percentage updated: {:?}", res)
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use validator_client::nymd::VestingSigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub profit_percent: u8,
|
||||
|
||||
#[clap(long)]
|
||||
pub gas: Option<u64>,
|
||||
}
|
||||
|
||||
pub async fn vesting_update_profit_percent(client: SigningClient, args: Args) {
|
||||
info!("Update vesting mix node profit percent - get those rewards!");
|
||||
|
||||
//profit percent between 1-100
|
||||
let res = client
|
||||
.vesting_update_mixnode_config(args.profit_percent, None)
|
||||
.await
|
||||
.expect("updating vesting mix-node profit percent");
|
||||
|
||||
info!("profit percentage updated: {:?}", res)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
use crate::context::SigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {}
|
||||
|
||||
pub async fn unbond_mixnode(_args: Args, client: SigningClient) {
|
||||
info!("Starting mixnode unbonding!");
|
||||
|
||||
let res = client
|
||||
.unbond_mixnode(None)
|
||||
.await
|
||||
.expect("failed to unbond mixnode!");
|
||||
|
||||
info!("Unbonding result: {:?}", res)
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::{info, warn};
|
||||
use mixnet_contract_common::Coin;
|
||||
use mixnet_contract_common::MixNode;
|
||||
use network_defaults::{
|
||||
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
|
||||
};
|
||||
use validator_client::nymd::VestingSigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub 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 async fn vesting_bond_mixnode(client: SigningClient, args: Args, denom: &str) {
|
||||
info!("Starting vesting 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 = 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
|
||||
.vesting_bond_mixnode(mixnode, &*args.signature, coin.into(), None)
|
||||
.await
|
||||
.expect("failed to bond vesting mixnode!");
|
||||
|
||||
info!("Bonding vesting result: {:?}", res)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use validator_client::nymd::VestingSigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub gas: Option<u64>,
|
||||
}
|
||||
|
||||
pub async fn vesting_unbond_mixnode(client: SigningClient) {
|
||||
info!("Starting vesting mixnode unbonding!");
|
||||
|
||||
let res = client
|
||||
.vesting_unbond_mixnode(None)
|
||||
.await
|
||||
.expect("failed to unbond vesting mixnode!");
|
||||
|
||||
info!("Unbonding vesting result: {:?}", res)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod gateway;
|
||||
pub mod mixnode;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct MixnetOperators {
|
||||
#[clap(subcommand)]
|
||||
pub command: MixnetOperatorsCommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MixnetOperatorsCommands {
|
||||
/// Manage your mixnode
|
||||
Mixnode(mixnode::MixnetOperatorsMixnode),
|
||||
/// Manage your gateway
|
||||
Gateway(gateway::MixnetOperatorsGateway),
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod query_all_gateways;
|
||||
pub mod query_all_mixnodes;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct MixnetQuery {
|
||||
#[clap(subcommand)]
|
||||
pub command: MixnetQueryCommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MixnetQueryCommands {
|
||||
/// Query mixnodes
|
||||
Mixnodes(query_all_mixnodes::Args),
|
||||
/// Query gateways
|
||||
Gateways(query_all_gateways::Args),
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use comfy_table::Table;
|
||||
|
||||
use crate::context::QueryClientWithValidatorAPI;
|
||||
use crate::utils::{pretty_cosmwasm_coin, show_error};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser)]
|
||||
#[clap(help = "Optionally, the gateway to display")]
|
||||
pub identity_key: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn query(args: Args, client: &QueryClientWithValidatorAPI) {
|
||||
match client.validator_api.get_gateways().await {
|
||||
Ok(res) => match args.identity_key {
|
||||
Some(identity_key) => {
|
||||
let node = res.iter().find(|node| {
|
||||
node.gateway
|
||||
.identity_key
|
||||
.to_string()
|
||||
.eq_ignore_ascii_case(&identity_key)
|
||||
});
|
||||
println!(
|
||||
"{}",
|
||||
::serde_json::to_string_pretty(&node).expect("json formatting error")
|
||||
);
|
||||
}
|
||||
None => {
|
||||
let mut table = Table::new();
|
||||
|
||||
table.set_header(vec!["Identity Key", "Owner", "Host", "Bond", "Version"]);
|
||||
for node in res {
|
||||
table.add_row(vec![
|
||||
node.gateway.identity_key.to_string(),
|
||||
node.owner.to_string(),
|
||||
node.gateway.host.to_string(),
|
||||
pretty_cosmwasm_coin(&node.pledge_amount),
|
||||
node.gateway.version,
|
||||
]);
|
||||
}
|
||||
|
||||
println!("The gateways in the directory are:");
|
||||
println!("{table}");
|
||||
}
|
||||
},
|
||||
Err(e) => show_error(e),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use comfy_table::Table;
|
||||
|
||||
use crate::context::QueryClientWithValidatorAPI;
|
||||
use crate::utils::{pretty_cosmwasm_coin, show_error};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser)]
|
||||
#[clap(help = "Optionally, the mixnode to display")]
|
||||
pub identity_key: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn query(args: Args, client: &QueryClientWithValidatorAPI) {
|
||||
match client.validator_api.get_mixnodes().await {
|
||||
Ok(res) => match args.identity_key {
|
||||
Some(identity_key) => {
|
||||
let node = res.iter().find(|node| {
|
||||
node.mix_node
|
||||
.identity_key
|
||||
.to_string()
|
||||
.eq_ignore_ascii_case(&identity_key)
|
||||
});
|
||||
println!(
|
||||
"{}",
|
||||
::serde_json::to_string_pretty(&node).expect("json formatting error")
|
||||
);
|
||||
}
|
||||
None => {
|
||||
let mut table = Table::new();
|
||||
|
||||
table.set_header(vec![
|
||||
"Identity Key",
|
||||
"Owner",
|
||||
"Host",
|
||||
"Bond",
|
||||
"Total Delegations",
|
||||
"Version",
|
||||
]);
|
||||
for node in res {
|
||||
table.add_row(vec![
|
||||
node.mix_node.identity_key.to_string(),
|
||||
node.owner.to_string(),
|
||||
node.mix_node.host.to_string(),
|
||||
pretty_cosmwasm_coin(&node.pledge_amount),
|
||||
pretty_cosmwasm_coin(&node.total_delegation()),
|
||||
node.mix_node.version,
|
||||
]);
|
||||
}
|
||||
|
||||
println!("The mixnodes in the directory are:");
|
||||
println!("{table}");
|
||||
}
|
||||
},
|
||||
Err(e) => show_error(e),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod account;
|
||||
pub mod block;
|
||||
pub mod cosmwasm;
|
||||
pub mod mixnet;
|
||||
pub mod signature;
|
||||
pub mod transactions;
|
||||
pub mod vesting;
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Errors {
|
||||
#[error("signature error - {0}")]
|
||||
SignatureError(#[from] k256::ecdsa::signature::Error),
|
||||
|
||||
#[error("{0}")]
|
||||
CosmrsError(#[from] cosmrs::ErrorReport),
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use cosmrs::crypto::secp256k1::{Signature, VerifyingKey};
|
||||
use cosmrs::crypto::PublicKey;
|
||||
use k256::ecdsa::signature::Verifier;
|
||||
|
||||
use crate::validator::signature::errors::Errors;
|
||||
|
||||
pub fn secp256k1_verify_with_public_key(
|
||||
public_key_as_bytes: &[u8],
|
||||
signature_as_hex: String,
|
||||
message: String,
|
||||
) -> Result<(), k256::ecdsa::signature::Error> {
|
||||
let verifying_key = VerifyingKey::from_sec1_bytes(public_key_as_bytes)?;
|
||||
let signature = Signature::from_str(&signature_as_hex)?;
|
||||
let message_as_bytes = message.into_bytes();
|
||||
verifying_key.verify(&message_as_bytes, &signature)
|
||||
}
|
||||
|
||||
pub fn secp256k1_verify_with_public_key_json(
|
||||
public_key_as_json: String,
|
||||
signature_as_hex: String,
|
||||
message: String,
|
||||
) -> Result<(), Errors> {
|
||||
let public_key = PublicKey::from_json(&public_key_as_json)?;
|
||||
let verifying_key = VerifyingKey::from_sec1_bytes(&public_key.to_bytes())?;
|
||||
let signature = Signature::from_str(&signature_as_hex)?;
|
||||
let message_as_bytes = message.into_bytes();
|
||||
Ok(verifying_key.verify(&message_as_bytes, &signature)?)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_secp256k1 {
|
||||
use crate::validator::signature::helpers::{
|
||||
secp256k1_verify_with_public_key, secp256k1_verify_with_public_key_json,
|
||||
};
|
||||
use cosmrs::crypto::PublicKey;
|
||||
|
||||
#[test]
|
||||
fn test_verify_with_json_public_key_with_valid_signature() {
|
||||
let json_public_key = r#"{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A4FdhUMasPmNhRZjtpKlmjNbq7EEUgPxfdI+E3vSajvc"}"#;
|
||||
let signature_as_hex = "E3AA5AC0DA1B7DEBB7808000F719D8ACB9A0BE10AFA2756A788516268EB246A1257EC1097C5E364EF916145B01641DEDFE955994CB340BDAFA99A65BCA3F6F28".to_string();
|
||||
let message = "test 1234".to_string();
|
||||
|
||||
let public_key = PublicKey::from_json(json_public_key).unwrap();
|
||||
let public_key_bytes = public_key.to_bytes();
|
||||
|
||||
let result = secp256k1_verify_with_public_key(&public_key_bytes, signature_as_hex, message);
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_with_json_public_key_with_invalid_signature() {
|
||||
let json_public_key = r#"{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A4FdhUMasPmNhRZjtpKlmjNbq7EEUgPxfdI+E3vSajvc"}"#;
|
||||
let signature_as_hex = "E3AA5AC0DA1B7DEBB7808000F719D8ACB9A0BE10AFA2756A788516268EB246A1257EC1097C5E364EF916145B01641DEDFE955994CB340BDAFA99A65BCA3F6F28".to_string();
|
||||
let message = "abcdef".to_string();
|
||||
|
||||
let public_key = PublicKey::from_json(json_public_key).unwrap();
|
||||
let public_key_bytes = public_key.to_bytes();
|
||||
|
||||
let result = secp256k1_verify_with_public_key(&public_key_bytes, signature_as_hex, message);
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_json_public_key_succeeds() {
|
||||
let json_public_key = r#"{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A4FdhUMasPmNhRZjtpKlmjNbq7EEUgPxfdI+E3vSajvc"}"#.to_string();
|
||||
let signature_as_hex = "E3AA5AC0DA1B7DEBB7808000F719D8ACB9A0BE10AFA2756A788516268EB246A1257EC1097C5E364EF916145B01641DEDFE955994CB340BDAFA99A65BCA3F6F28".to_string();
|
||||
let message = "test 1234".to_string();
|
||||
|
||||
let result =
|
||||
secp256k1_verify_with_public_key_json(json_public_key, signature_as_hex, message);
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_public_key_fails_with_error() {
|
||||
let bad_json_public_key = r#"This is not JSON ☠️"#.to_string();
|
||||
let signature_as_hex = "E3AA5AC0DA1B7DEBB7808000F719D8ACB9A0BE10AFA2756A788516268EB246A1257EC1097C5E364EF916145B01641DEDFE955994CB340BDAFA99A65BCA3F6F28".to_string();
|
||||
let message = "abcdef".to_string();
|
||||
|
||||
let result =
|
||||
secp256k1_verify_with_public_key_json(bad_json_public_key, signature_as_hex, message);
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod errors;
|
||||
pub mod helpers;
|
||||
pub mod sign;
|
||||
pub mod verify;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct Signature {
|
||||
#[clap(subcommand)]
|
||||
pub command: Option<SignatureCommands>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum SignatureCommands {
|
||||
/// Sign a message
|
||||
Sign(crate::validator::signature::sign::Args),
|
||||
/// Verify a message
|
||||
Verify(crate::validator::signature::verify::Args),
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::utils::show_error;
|
||||
use clap::Parser;
|
||||
use cosmrs::crypto::PublicKey;
|
||||
use log::error;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
use validator_client::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct SignatureOutputJson {
|
||||
pub account_id: String,
|
||||
pub public_key: PublicKey,
|
||||
pub signature: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser)]
|
||||
#[clap(help = "The message to sign")]
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
pub fn sign(args: Args, prefix: &str, mnemonic: Option<bip39::Mnemonic>) {
|
||||
if args.message.trim().is_empty() {
|
||||
error!("Message is empty or contains only whitespace");
|
||||
return;
|
||||
}
|
||||
|
||||
if mnemonic.is_none() {
|
||||
error!(
|
||||
"Please provide the mnemonic as an argument or using the MNEMONIC environment variable"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
match DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic.expect("mnemonic not set")) {
|
||||
Ok(wallet) => match wallet.try_derive_accounts() {
|
||||
Ok(accounts) => match accounts.first() {
|
||||
Some(account) => {
|
||||
let msg = args.message.into_bytes();
|
||||
match wallet.sign_raw_with_account(account, &msg) {
|
||||
Ok(signature) => {
|
||||
let output = SignatureOutputJson {
|
||||
account_id: account.address().to_string(),
|
||||
public_key: account.public_key(),
|
||||
signature: signature.to_string(),
|
||||
};
|
||||
println!("{}", json!(output));
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to sign message. {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
error!("Could not derive an account key from the mnemonic",)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to derive accounts. {}", e);
|
||||
}
|
||||
},
|
||||
Err(e) => show_error(e),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::Parser;
|
||||
use cosmrs::crypto::PublicKey;
|
||||
use log::{error, info};
|
||||
use serde_json::json;
|
||||
|
||||
use validator_client::nymd::AccountId;
|
||||
|
||||
use crate::context::QueryClient;
|
||||
use crate::validator::signature::helpers::secp256k1_verify_with_public_key;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser)]
|
||||
#[clap(
|
||||
help = "The public key of the account, or the account id to query for a public key (NOTE: the account must have signed a message stored on the chain for the public key record to exist)"
|
||||
)]
|
||||
pub public_key_or_address: String,
|
||||
|
||||
#[clap(value_parser)]
|
||||
#[clap(help = "The signature to verify as hex")]
|
||||
pub signature_as_hex: String,
|
||||
|
||||
#[clap(value_parser)]
|
||||
#[clap(help = "The message to verify as a string")]
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
pub async fn verify(args: Args, client: &QueryClient) {
|
||||
if args.public_key_or_address.trim().is_empty() {
|
||||
error!("Please ensure the public key or address is not empty or whitespace");
|
||||
return;
|
||||
}
|
||||
|
||||
let public_key = match AccountId::from_str(&args.public_key_or_address) {
|
||||
Ok(address) => {
|
||||
info!("Found account address instead of public key, so looking up public key for {} from chain", address);
|
||||
match client.get_account_public_key(&address).await.ok() {
|
||||
Some(public_key) => {
|
||||
if let Some(k) = public_key {
|
||||
info!("Found public key {}", json!(k));
|
||||
}
|
||||
public_key
|
||||
}
|
||||
None => {
|
||||
error!(
|
||||
"Address {} does not have a public key recorded on the chain. This is probably because the account has never signed a transaction.",
|
||||
address
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => match PublicKey::from_json(&args.public_key_or_address) {
|
||||
Ok(parsed) => Some(parsed),
|
||||
Err(e) => {
|
||||
error!("Public key should be JSON. Unable to parse: {}", e);
|
||||
None
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
match public_key {
|
||||
Some(public_key) => {
|
||||
if public_key.type_url() != PublicKey::SECP256K1_TYPE_URL {
|
||||
error!("Sorry, we only support secp256k1 public keys at the moment");
|
||||
return;
|
||||
}
|
||||
|
||||
match secp256k1_verify_with_public_key(
|
||||
&public_key.to_bytes(),
|
||||
args.signature_as_hex,
|
||||
args.message,
|
||||
) {
|
||||
Ok(()) => println!("SUCCESS ✅ signature verified"),
|
||||
Err(e) => {
|
||||
error!("FAILURE ❌ Signature verification failed: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
error!("Unable to verify, as unable to get the public key");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::context::QueryClient;
|
||||
use crate::utils::show_error;
|
||||
use cosmrs::tx::Hash;
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser)]
|
||||
#[clap(help = "The transaction hash")]
|
||||
pub tx_hash: String,
|
||||
}
|
||||
|
||||
pub async fn get(args: Args, client: &QueryClient) {
|
||||
let hash = Hash::from_str(&args.tx_hash).expect("could not parse transaction hash");
|
||||
|
||||
match client.get_tx(hash).await {
|
||||
Ok(res) => {
|
||||
println!("{}", json!(res))
|
||||
}
|
||||
Err(e) => show_error(e),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod get_transaction;
|
||||
pub mod query_transactions;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct Transactions {
|
||||
#[clap(subcommand)]
|
||||
pub command: Option<TransactionsCommands>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum TransactionsCommands {
|
||||
/// Get a transaction by hash or block height
|
||||
Get(crate::validator::transactions::get_transaction::Args),
|
||||
/// Query for transactions
|
||||
Query(crate::validator::transactions::query_transactions::Args),
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::Parser;
|
||||
use cosmrs::rpc::query::Query;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::context::QueryClient;
|
||||
use crate::utils::show_error;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser)]
|
||||
#[clap(help = "The query to execute")]
|
||||
pub query: String,
|
||||
}
|
||||
|
||||
pub async fn query(args: Args, client: &QueryClient) {
|
||||
match Query::from_str(&args.query) {
|
||||
Ok(query) => match client.search_tx(query).await {
|
||||
Ok(res) => {
|
||||
println!("{}", json!(res))
|
||||
}
|
||||
Err(e) => show_error(e),
|
||||
},
|
||||
Err(e) => show_error(e),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use cosmrs::AccountId;
|
||||
use log::info;
|
||||
|
||||
use validator_client::nymd::{Coin, VestingQueryClient};
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use crate::utils::show_error;
|
||||
use crate::utils::{pretty_coin, pretty_cosmwasm_coin};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser)]
|
||||
#[clap(help = "Optionally, the account address to get the balance for")]
|
||||
pub address: Option<AccountId>,
|
||||
}
|
||||
|
||||
pub async fn balance(args: Args, client: SigningClient) {
|
||||
let account_id = args.address.unwrap_or_else(|| client.address().clone());
|
||||
let vesting_address = account_id.to_string();
|
||||
let denom = client.current_chain_details().mix_denom.base.as_str();
|
||||
|
||||
info!(
|
||||
"Getting vesting schedule information for {}...",
|
||||
&vesting_address
|
||||
);
|
||||
|
||||
let original_vesting = client.original_vesting(&vesting_address).await;
|
||||
|
||||
match original_vesting {
|
||||
Ok(res) => {
|
||||
let spendable_coins = client
|
||||
.spendable_coins(&vesting_address, None)
|
||||
.await
|
||||
.unwrap_or_else(|_| Coin::new(0u128, denom));
|
||||
let liquid_account_balance = client
|
||||
.get_balance(&account_id, denom.to_string())
|
||||
.await
|
||||
.unwrap_or(None)
|
||||
.unwrap_or_else(|| Coin::new(0u128, denom));
|
||||
|
||||
println!(
|
||||
"Account {} has\n{} vested with\n{} available to be withdrawn to the main account (balance {})",
|
||||
&account_id,
|
||||
pretty_cosmwasm_coin(&res.amount),
|
||||
pretty_coin(&spendable_coins),
|
||||
pretty_coin(&liquid_account_balance),
|
||||
);
|
||||
}
|
||||
Err(e) => show_error(e),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
use mixnet_contract_common::Coin;
|
||||
use network_defaults::NymNetworkDetails;
|
||||
use validator_client::nymd::AccountId;
|
||||
use validator_client::nymd::VestingSigningClient;
|
||||
use validator_client::nymd::{CosmosCoin, Denom};
|
||||
use vesting_contract_common::messages::VestingSpecification;
|
||||
|
||||
use crate::context::SigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub periods_seconds: Option<u64>,
|
||||
|
||||
#[clap(long)]
|
||||
pub number_of_periods: Option<u64>,
|
||||
|
||||
#[clap(long)]
|
||||
pub start_time: Option<u64>,
|
||||
|
||||
#[clap(long)]
|
||||
pub address: String,
|
||||
|
||||
#[clap(long)]
|
||||
pub amount: u64,
|
||||
|
||||
#[clap(long)]
|
||||
pub staking_address: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn create(args: Args, client: SigningClient, network_details: &NymNetworkDetails) {
|
||||
info!("Creating vesting schedule!");
|
||||
|
||||
let vesting = VestingSpecification::new(
|
||||
args.start_time,
|
||||
args.periods_seconds,
|
||||
args.number_of_periods,
|
||||
);
|
||||
|
||||
let denom = network_details.chain_details.mix_denom.base.to_string();
|
||||
|
||||
let coin = Coin::new(args.amount.into(), &denom);
|
||||
|
||||
let res = client
|
||||
.create_periodic_vesting_account(
|
||||
&*args.address,
|
||||
args.staking_address,
|
||||
Some(vesting),
|
||||
coin.into(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("creating vesting schedule for the user!");
|
||||
|
||||
//send 1 coin
|
||||
let coin_amount: u64 = 1_000_000;
|
||||
|
||||
let coin = CosmosCoin {
|
||||
denom: Denom::from_str(&denom).unwrap(),
|
||||
amount: coin_amount.into(),
|
||||
};
|
||||
|
||||
let send_coin_response = client
|
||||
.send(
|
||||
&AccountId::from_str(&*args.address).unwrap(),
|
||||
vec![coin.into()],
|
||||
"payment made :)",
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
info!("Vesting result: {:?}", res);
|
||||
info!("Coin send result: {:?}", send_coin_response);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod balance;
|
||||
pub mod create_vesting_schedule;
|
||||
pub mod query_vesting_schedule;
|
||||
pub mod withdraw_vested;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct VestingSchedule {
|
||||
#[clap(subcommand)]
|
||||
pub command: Option<VestingScheduleCommands>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum VestingScheduleCommands {
|
||||
/// Creates a vesting schedule
|
||||
Create(crate::validator::vesting::create_vesting_schedule::Args),
|
||||
/// Query for vesting schedule
|
||||
Query(crate::validator::vesting::query_vesting_schedule::Args),
|
||||
/// Get the amount that has vested and is free for withdrawal, delegation or bonding
|
||||
VestedBalance(crate::validator::vesting::balance::Args),
|
||||
/// Withdraw vested tokens (note: the available amount excludes anything delegated or bonded before or after vesting)
|
||||
WithdrawVested(crate::validator::vesting::withdraw_vested::Args),
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use cosmrs::AccountId;
|
||||
use cosmwasm_std::Coin as CosmWasmCoin;
|
||||
use log::info;
|
||||
|
||||
use validator_client::nymd::{Coin, VestingQueryClient};
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use crate::utils::show_error;
|
||||
use crate::utils::{pretty_coin, pretty_cosmwasm_coin};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser)]
|
||||
#[clap(help = "Optionally, the account address to get the balance for")]
|
||||
pub address: Option<AccountId>,
|
||||
}
|
||||
|
||||
pub async fn query(args: Args, client: SigningClient) {
|
||||
let account_id = args.address.unwrap_or_else(|| client.address().clone());
|
||||
let vesting_address = account_id.to_string();
|
||||
let denom = client.current_chain_details().mix_denom.base.as_str();
|
||||
|
||||
info!(
|
||||
"Getting vesting schedule information for {}...",
|
||||
&vesting_address
|
||||
);
|
||||
|
||||
let liquid_account_balance = client
|
||||
.get_balance(&account_id, denom.to_string())
|
||||
.await
|
||||
.unwrap_or(None)
|
||||
.unwrap_or_else(|| Coin::new(0u128, denom));
|
||||
let original_vesting = client.original_vesting(&vesting_address).await;
|
||||
let start_time = client.vesting_start_time(&vesting_address).await;
|
||||
let end_time = client.vesting_end_time(&vesting_address).await;
|
||||
let vested_coins = client.vested_coins(&vesting_address, None).await;
|
||||
let spendable_coins = client.spendable_coins(&vesting_address, None).await;
|
||||
let locked_coins = client.locked_coins(&vesting_address, None).await;
|
||||
|
||||
// TODO: get better copy text for what these are
|
||||
let vesting_coins = client.vesting_coins(&vesting_address, None).await;
|
||||
let delegated_vesting = client.delegated_vesting(&vesting_address, None).await;
|
||||
let delegated_free = client.delegated_free(&vesting_address, None).await;
|
||||
|
||||
original_vesting.as_ref().map_or_else(show_error, |res| {
|
||||
println!(
|
||||
"Amount: {} ({})",
|
||||
pretty_cosmwasm_coin(&res.amount),
|
||||
res.amount
|
||||
);
|
||||
println!("No of periods: {}", res.number_of_periods);
|
||||
println!(
|
||||
"Duration each: {}",
|
||||
time::Duration::seconds(res.period_duration as i64)
|
||||
);
|
||||
});
|
||||
|
||||
start_time.as_ref().map_or_else(show_error, |res| {
|
||||
println!(
|
||||
"Start date: {}",
|
||||
time::OffsetDateTime::from_unix_timestamp(res.seconds() as i64)
|
||||
.expect("unable to parse vesting start timestamp")
|
||||
.date()
|
||||
);
|
||||
});
|
||||
|
||||
end_time.map_or_else(show_error, |res| {
|
||||
println!(
|
||||
"End date: {}",
|
||||
time::OffsetDateTime::from_unix_timestamp(res.seconds() as i64)
|
||||
.expect("unable to parse vesting end timestamp")
|
||||
.date()
|
||||
);
|
||||
});
|
||||
|
||||
vested_coins.map_or_else(show_error, |res| {
|
||||
println!("Vested balance: {} ({})", pretty_coin(&res), res);
|
||||
});
|
||||
|
||||
if let Ok(res) = original_vesting {
|
||||
if let Ok(start) = start_time {
|
||||
let amount_in_each_period = res.amount.amount.u128() / res.number_of_periods as u128;
|
||||
let coin_in_each_period = CosmWasmCoin::new(amount_in_each_period, denom);
|
||||
println!();
|
||||
println!("Vesting schedule:");
|
||||
for period in 1..(res.number_of_periods as u64 + 1) {
|
||||
let date = time::OffsetDateTime::from_unix_timestamp(
|
||||
(start.seconds() + period * res.period_duration) as i64,
|
||||
)
|
||||
.expect("unable to parse vesting start timestamp")
|
||||
.date();
|
||||
let amount_in_vested =
|
||||
period as u128 * res.amount.amount.u128() / res.number_of_periods as u128;
|
||||
let coin_in_vested = CosmWasmCoin::new(amount_in_vested, denom);
|
||||
println!(
|
||||
"{}. {} {} => {}",
|
||||
period,
|
||||
date,
|
||||
pretty_cosmwasm_coin(&coin_in_each_period),
|
||||
pretty_cosmwasm_coin(&coin_in_vested),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spendable_coins.map_or_else(show_error, |res| {
|
||||
println!();
|
||||
println!("This account has the following vested tokens available either to be withdrawn to the main account, or to be delegated:");
|
||||
println!("Spendable coins: {} ({})", pretty_coin(&res), res);
|
||||
});
|
||||
|
||||
locked_coins.map_or_else(show_error, |res| {
|
||||
println!();
|
||||
if res.amount > 0 {
|
||||
println!("This account has delegated more than the current cap, so the following balance is unavailable for bonding or delegation:");
|
||||
println!("Locked balance: {} ({})", pretty_coin(&res), res);
|
||||
} else {
|
||||
println!("This account is not capped and can use the spendable balance for bonding or delegations:");
|
||||
println!("Locked balance: {} ({})", pretty_coin(&res), res);
|
||||
}
|
||||
});
|
||||
|
||||
println!();
|
||||
println!("The following are shown for information (more help text will follow soon):");
|
||||
vesting_coins.map_or_else(show_error, |res| {
|
||||
println!("Vesting coins: {} ({})", pretty_coin(&res), res);
|
||||
});
|
||||
delegated_vesting.map_or_else(show_error, |res| {
|
||||
println!("Delegated vesting: {} ({})", pretty_coin(&res), res);
|
||||
});
|
||||
delegated_free.map_or_else(show_error, |res| {
|
||||
println!("Delegation free: {} ({})", pretty_coin(&res), res);
|
||||
});
|
||||
|
||||
println!();
|
||||
println!(
|
||||
"The main account {} also has a regular balance of:",
|
||||
&account_id
|
||||
);
|
||||
println!(
|
||||
"{} ({})",
|
||||
pretty_coin(&liquid_account_balance),
|
||||
&liquid_account_balance
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
use validator_client::nymd::{Coin, VestingQueryClient, VestingSigningClient};
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use crate::utils::show_error;
|
||||
use crate::utils::{pretty_coin, pretty_cosmwasm_coin};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser)]
|
||||
#[clap(help = "Amount to transfer in micro denomination (e.g. unym or unyx)")]
|
||||
pub amount: u128,
|
||||
}
|
||||
|
||||
pub async fn execute(args: Args, client: SigningClient) {
|
||||
let account_id = client.address();
|
||||
let vesting_address = account_id.to_string();
|
||||
let denom = client.current_chain_details().mix_denom.base.as_str();
|
||||
|
||||
info!(
|
||||
"Getting vesting schedule information for {}...",
|
||||
&vesting_address
|
||||
);
|
||||
|
||||
let original_vesting = client.original_vesting(&vesting_address).await;
|
||||
|
||||
match original_vesting {
|
||||
Ok(res) => {
|
||||
let spendable_coins = client
|
||||
.spendable_coins(&vesting_address, None)
|
||||
.await
|
||||
.unwrap_or_else(|_| Coin::new(0u128, denom));
|
||||
let liquid_account_balance = client
|
||||
.get_balance(account_id, denom.to_string())
|
||||
.await
|
||||
.unwrap_or(None)
|
||||
.unwrap_or_else(|| Coin::new(0u128, denom));
|
||||
|
||||
println!(
|
||||
"Account {} has\n{} vested with {} available to be withdrawn to the main account (balance {})",
|
||||
&account_id,
|
||||
pretty_cosmwasm_coin(&res.amount),
|
||||
pretty_coin(&spendable_coins),
|
||||
pretty_coin(&liquid_account_balance),
|
||||
);
|
||||
println!();
|
||||
|
||||
// execute withdraw
|
||||
|
||||
let amount = Coin {
|
||||
amount: args.amount,
|
||||
denom: denom.to_string(),
|
||||
};
|
||||
|
||||
info!(
|
||||
"Withdrawing {} ({}) from {}...",
|
||||
pretty_coin(&amount),
|
||||
&amount,
|
||||
&account_id
|
||||
);
|
||||
|
||||
match client.withdraw_vested_coins(amount, None).await {
|
||||
Ok(res) => {
|
||||
println!();
|
||||
println!("SUCCESS ✅");
|
||||
println!(
|
||||
"Nodesguru: https://nym.explorers.guru/transaction/{}",
|
||||
&res.transaction_hash
|
||||
);
|
||||
println!(
|
||||
"Mintscan: https://www.mintscan.io/nyx/txs/{}",
|
||||
&res.transaction_hash
|
||||
);
|
||||
println!("Transaction hash: {}", &res.transaction_hash);
|
||||
println!("Gas used: {}", &res.gas_info.gas_used);
|
||||
println!();
|
||||
}
|
||||
Err(e) => show_error(e),
|
||||
}
|
||||
|
||||
// query for balances again
|
||||
let res = client
|
||||
.original_vesting(&vesting_address)
|
||||
.await
|
||||
.expect("vesting account does not exist");
|
||||
let spendable_coins = client
|
||||
.spendable_coins(&vesting_address, None)
|
||||
.await
|
||||
.unwrap_or_else(|_| Coin::new(0u128, denom));
|
||||
|
||||
let liquid_account_balance = client
|
||||
.get_balance(account_id, denom.to_string())
|
||||
.await
|
||||
.unwrap_or(None)
|
||||
.unwrap_or_else(|| Coin::new(0u128, denom));
|
||||
|
||||
println!(
|
||||
"After withdrawal, account {} has\n{} vested with {} available to be withdrawn to the main account (balance {})",
|
||||
&account_id,
|
||||
pretty_cosmwasm_coin(&res.amount),
|
||||
pretty_coin(&spendable_coins),
|
||||
pretty_coin(&liquid_account_balance),
|
||||
);
|
||||
}
|
||||
Err(e) => show_error(e),
|
||||
}
|
||||
}
|
||||
@@ -166,6 +166,7 @@ pub mod fixed_U128_as_string {
|
||||
use super::U128;
|
||||
use serde::de::Error;
|
||||
use serde::Deserialize;
|
||||
#[allow(unused_imports)]
|
||||
use std::str::FromStr;
|
||||
|
||||
pub fn serialize<S>(val: &U128, serializer: S) -> Result<S::Ok, S::Error>
|
||||
|
||||
@@ -216,7 +216,6 @@ pub enum QueryMsg {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct MigrateMsg {
|
||||
pub mixnet_denom: String,
|
||||
pub nodes_to_remove: Option<Vec<NodeToRemove>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,7 @@ pub struct InitMsg {
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct MigrateMsg {
|
||||
pub mix_denom: String,
|
||||
}
|
||||
pub struct MigrateMsg {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)]
|
||||
pub struct VestingSpecification {
|
||||
@@ -119,6 +117,11 @@ pub enum ExecuteMsg {
|
||||
UpdateLockedPledgeCap {
|
||||
amount: Uint128,
|
||||
},
|
||||
MigrateHeightsToTimestamps {
|
||||
account_id: u32,
|
||||
mix_identity: String,
|
||||
height_timestamp_map: Vec<(u64, u64)>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
|
||||
@@ -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"
|
||||
@@ -28,7 +28,6 @@ use crate::mixnodes::bonding_queries::{
|
||||
};
|
||||
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,
|
||||
};
|
||||
@@ -509,7 +508,6 @@ fn remove_malicious_node(
|
||||
|
||||
#[entry_point]
|
||||
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)
|
||||
|
||||
@@ -1,37 +1,2 @@
|
||||
// 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 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())
|
||||
}
|
||||
|
||||
@@ -22,4 +22,4 @@ cw-storage-plus = { version = "0.13.4", features = ["iterator"] }
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
||||
thiserror = { version = "1.0" }
|
||||
thiserror = { version = "1.0" }
|
||||
@@ -1,8 +1,8 @@
|
||||
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, BlockTimestampSecs, ADMIN,
|
||||
DELEGATIONS, MIXNET_CONTRACT_ADDRESS, MIX_DENOM,
|
||||
account_from_address, locked_pledge_cap, remove_delegation, save_delegation,
|
||||
update_locked_pledge_cap, BlockTimestampSecs, ADMIN, DELEGATIONS, MIXNET_CONTRACT_ADDRESS,
|
||||
MIX_DENOM,
|
||||
};
|
||||
use crate::traits::{
|
||||
DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, VestingAccount,
|
||||
@@ -30,6 +30,7 @@ use vesting_contract_common::{
|
||||
|
||||
pub const INITIAL_LOCKED_PLEDGE_CAP: Uint128 = Uint128::new(100_000_000_000);
|
||||
|
||||
/// Instantiate the contract
|
||||
#[entry_point]
|
||||
pub fn instantiate(
|
||||
deps: DepsMut<'_>,
|
||||
@@ -37,7 +38,7 @@ pub fn instantiate(
|
||||
info: MessageInfo,
|
||||
msg: InitMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
// ADMIN is set to the address that instantiated the contract, TODO: make this updatable
|
||||
//! ADMIN is set to the address that instantiated the contract
|
||||
ADMIN.save(deps.storage, &info.sender.to_string())?;
|
||||
MIXNET_CONTRACT_ADDRESS.save(deps.storage, &msg.mixnet_contract_address)?;
|
||||
MIX_DENOM.save(deps.storage, &msg.mix_denom)?;
|
||||
@@ -46,7 +47,6 @@ 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())
|
||||
}
|
||||
|
||||
@@ -129,9 +129,23 @@ pub fn execute(
|
||||
ExecuteMsg::UpdateStakingAddress { to_address } => {
|
||||
try_update_staking_address(to_address, info, deps)
|
||||
}
|
||||
ExecuteMsg::MigrateHeightsToTimestamps {
|
||||
account_id,
|
||||
mix_identity,
|
||||
height_timestamp_map,
|
||||
} => try_migrate_heights_to_timestamps(
|
||||
account_id,
|
||||
mix_identity,
|
||||
height_timestamp_map,
|
||||
info,
|
||||
deps,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update locked_pledge_cap, the hard cap for staking/bonding with unvested tokens.
|
||||
///
|
||||
/// Callable by ADMIN only, see [instantiate].
|
||||
pub fn try_update_locked_pledge_cap(
|
||||
amount: Uint128,
|
||||
info: MessageInfo,
|
||||
@@ -144,6 +158,7 @@ pub fn try_update_locked_pledge_cap(
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
/// Update config for a mixnode bonded with vesting account, sends [mixnet_contract_common::ExecuteMsg::UpdateMixnodeConfig] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||
pub fn try_update_mixnode_config(
|
||||
profit_margin_percent: u8,
|
||||
info: MessageInfo,
|
||||
@@ -153,7 +168,9 @@ pub fn try_update_mixnode_config(
|
||||
account.try_update_mixnode_config(profit_margin_percent, deps.storage)
|
||||
}
|
||||
|
||||
// Only contract admin, set at init
|
||||
/// Updates mixnet contract address, for cases when a new mixnet contract is deployed.
|
||||
///
|
||||
/// Callable by ADMIN only, see [instantiate].
|
||||
pub fn try_update_mixnet_address(
|
||||
address: String,
|
||||
info: MessageInfo,
|
||||
@@ -166,7 +183,7 @@ pub fn try_update_mixnet_address(
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
// Only contract owner of vesting account
|
||||
/// Withdraw already vested coins.
|
||||
pub fn try_withdraw_vested_coins(
|
||||
amount: Coin,
|
||||
env: Env,
|
||||
@@ -207,6 +224,7 @@ pub fn try_withdraw_vested_coins(
|
||||
}
|
||||
}
|
||||
|
||||
/// Transfer ownership of the entire vesting account.
|
||||
fn try_transfer_ownership(
|
||||
to_address: String,
|
||||
info: MessageInfo,
|
||||
@@ -223,6 +241,7 @@ fn try_transfer_ownership(
|
||||
}
|
||||
}
|
||||
|
||||
/// Set or update staking address for a vesting account.
|
||||
fn try_update_staking_address(
|
||||
to_address: Option<String>,
|
||||
info: MessageInfo,
|
||||
@@ -240,7 +259,31 @@ fn try_update_staking_address(
|
||||
}
|
||||
}
|
||||
|
||||
// Owner or staking
|
||||
pub fn try_migrate_heights_to_timestamps(
|
||||
account_id: u32,
|
||||
mix_identity: String,
|
||||
height_timestamp_map: Vec<(u64, u64)>,
|
||||
info: MessageInfo,
|
||||
deps: DepsMut<'_>,
|
||||
) -> Result<Response, ContractError> {
|
||||
if info.sender != ADMIN.load(deps.storage)? {
|
||||
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
|
||||
}
|
||||
|
||||
for (height, timestamp) in height_timestamp_map {
|
||||
let amount = DELEGATIONS.load(deps.storage, (account_id, mix_identity.clone(), height))?;
|
||||
remove_delegation((account_id, mix_identity.clone(), height), deps.storage)?;
|
||||
save_delegation(
|
||||
(account_id, mix_identity.clone(), timestamp),
|
||||
amount,
|
||||
deps.storage,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
/// Bond a gateway, sends [mixnet_contract_common::ExecuteMsg::BondGatewayOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||
pub fn try_bond_gateway(
|
||||
gateway: Gateway,
|
||||
owner_signature: String,
|
||||
@@ -255,11 +298,13 @@ pub fn try_bond_gateway(
|
||||
account.try_bond_gateway(gateway, owner_signature, pledge, &env, deps.storage)
|
||||
}
|
||||
|
||||
/// Unbond a gateway, sends [mixnet_contract_common::ExecuteMsg::UnbondGatewayOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||
pub fn try_unbond_gateway(info: MessageInfo, deps: DepsMut<'_>) -> Result<Response, ContractError> {
|
||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||
account.try_unbond_gateway(deps.storage)
|
||||
}
|
||||
|
||||
/// Track gateway unbonding, invoked by the mixnet contract after succesful unbonding, message containes coins returned including any accrued rewards.
|
||||
pub fn try_track_unbond_gateway(
|
||||
owner: &str,
|
||||
amount: Coin,
|
||||
@@ -274,6 +319,7 @@ pub fn try_track_unbond_gateway(
|
||||
Ok(Response::new().add_event(new_track_gateway_unbond_event()))
|
||||
}
|
||||
|
||||
/// Compound operator reward, sends [mixnet_contract_common::ExecuteMsg::CompoundOperatorRewardOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS], adds available rewards to the existing bond
|
||||
pub fn try_compound_operator_reward(
|
||||
info: MessageInfo,
|
||||
deps: DepsMut<'_>,
|
||||
@@ -282,6 +328,7 @@ pub fn try_compound_operator_reward(
|
||||
account.try_compound_operator_reward(deps.storage)
|
||||
}
|
||||
|
||||
/// Bond a mixnode, sends [mixnet_contract_common::ExecuteMsg::BondMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||
pub fn try_bond_mixnode(
|
||||
mix_node: MixNode,
|
||||
owner_signature: String,
|
||||
@@ -296,11 +343,13 @@ pub fn try_bond_mixnode(
|
||||
account.try_bond_mixnode(mix_node, owner_signature, pledge, &env, deps.storage)
|
||||
}
|
||||
|
||||
/// Unbond a mixnode, sends [mixnet_contract_common::ExecuteMsg::UnbondMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||
pub fn try_unbond_mixnode(info: MessageInfo, deps: DepsMut<'_>) -> Result<Response, ContractError> {
|
||||
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
||||
account.try_unbond_mixnode(deps.storage)
|
||||
}
|
||||
|
||||
/// Track mixnode unbonding, invoked by the mixnet contract after succesful unbonding, message containes coins returned including any accrued rewards.
|
||||
pub fn try_track_unbond_mixnode(
|
||||
owner: &str,
|
||||
amount: Coin,
|
||||
@@ -315,6 +364,7 @@ pub fn try_track_unbond_mixnode(
|
||||
Ok(Response::new().add_event(new_track_mixnode_unbond_event()))
|
||||
}
|
||||
|
||||
/// Track reward collection, invoked by the mixnert contract after sucessful reward compounding or claiming
|
||||
fn try_track_reward(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
@@ -329,6 +379,7 @@ fn try_track_reward(
|
||||
Ok(Response::new().add_event(new_track_reward_event()))
|
||||
}
|
||||
|
||||
/// Track undelegation, invoked by the mixnet contract after sucessful undelegation, message contains coins returned with any accrued rewards.
|
||||
fn try_track_undelegation(
|
||||
address: &str,
|
||||
mix_identity: IdentityKey,
|
||||
@@ -344,6 +395,7 @@ fn try_track_undelegation(
|
||||
Ok(Response::new().add_event(new_track_undelegation_event()))
|
||||
}
|
||||
|
||||
/// Delegate to mixnode, sends [mixnet_contract_common::ExecuteMsg::DelegateToMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS]..
|
||||
fn try_delegate_to_mixnode(
|
||||
mix_identity: IdentityKey,
|
||||
amount: Coin,
|
||||
@@ -357,6 +409,7 @@ fn try_delegate_to_mixnode(
|
||||
account.try_delegate_to_mixnode(mix_identity, amount, &env, deps.storage)
|
||||
}
|
||||
|
||||
/// Compounds deleagtor reward, ie adds it to the existing delegations for a node, sends [mixnet_contract_common::ExecuteMsg::CompoundDelegatorRewardOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||
fn try_compound_delegator_reward(
|
||||
mix_identity: IdentityKey,
|
||||
info: MessageInfo,
|
||||
@@ -366,6 +419,7 @@ fn try_compound_delegator_reward(
|
||||
account.try_compound_delegator_reward(mix_identity, deps.storage)
|
||||
}
|
||||
|
||||
/// Claims operator reward, sends [mixnet_contract_common::ExecuteMsg::ClaimOperatorRewardOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||
fn try_claim_operator_reward(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
@@ -374,6 +428,7 @@ fn try_claim_operator_reward(
|
||||
account.try_claim_operator_reward(deps.storage)
|
||||
}
|
||||
|
||||
/// Claims delegator reward, sends [mixnet_contract_common::ExecuteMsg::ClaimDelegatorRewardOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||
fn try_claim_delegator_reward(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
@@ -383,6 +438,7 @@ fn try_claim_delegator_reward(
|
||||
account.try_claim_delegator_reward(mix_identity, deps.storage)
|
||||
}
|
||||
|
||||
/// Undelegates from a mixnode, sends [mixnet_contract_common::ExecuteMsg::UndelegateFromMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
|
||||
fn try_undelegate_from_mixnode(
|
||||
mix_identity: IdentityKey,
|
||||
info: MessageInfo,
|
||||
@@ -392,6 +448,9 @@ fn try_undelegate_from_mixnode(
|
||||
account.try_undelegate_from_mixnode(mix_identity, deps.storage)
|
||||
}
|
||||
|
||||
/// Creates a new periodic vesting account, and deposits funds to vest into the contract.
|
||||
///
|
||||
/// Callable by ADMIN only, see [instantiate].
|
||||
fn try_create_periodic_vesting_account(
|
||||
owner_address: &str,
|
||||
staking_address: Option<String>,
|
||||
@@ -534,10 +593,12 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
|
||||
Ok(query_res?)
|
||||
}
|
||||
|
||||
/// Get locked_pledge_cap, the hard cap for staking/bonding with unvested tokens.
|
||||
pub fn get_locked_pledge_cap(deps: Deps<'_>) -> Uint128 {
|
||||
locked_pledge_cap(deps.storage)
|
||||
}
|
||||
|
||||
/// Get current vesting period for a given [crate::vesting::Account].
|
||||
pub fn try_get_current_vesting_period(
|
||||
address: &str,
|
||||
deps: Deps<'_>,
|
||||
@@ -547,11 +608,13 @@ pub fn try_get_current_vesting_period(
|
||||
Ok(account.get_current_vesting_period(env.block.time))
|
||||
}
|
||||
|
||||
/// Loads mixnode bond from vesting contract storage.
|
||||
pub fn try_get_mixnode(address: &str, deps: Deps<'_>) -> Result<Option<PledgeData>, ContractError> {
|
||||
let account = account_from_address(address, deps.storage, deps.api)?;
|
||||
account.load_mixnode_pledge(deps.storage)
|
||||
}
|
||||
|
||||
/// Loads gateway bond from vesting contract storage.
|
||||
pub fn try_get_gateway(address: &str, deps: Deps<'_>) -> Result<Option<PledgeData>, ContractError> {
|
||||
let account = account_from_address(address, deps.storage, deps.api)?;
|
||||
account.load_gateway_pledge(deps.storage)
|
||||
@@ -561,6 +624,7 @@ pub fn try_get_account(address: &str, deps: Deps<'_>) -> Result<Account, Contrac
|
||||
account_from_address(address, deps.storage, deps.api)
|
||||
}
|
||||
|
||||
/// Gets currently locked coins, see [crate::traits::VestingAccount::locked_coins]
|
||||
pub fn try_get_locked_coins(
|
||||
vesting_account_address: &str,
|
||||
block_time: Option<Timestamp>,
|
||||
@@ -571,6 +635,7 @@ pub fn try_get_locked_coins(
|
||||
account.locked_coins(block_time, &env, deps.storage)
|
||||
}
|
||||
|
||||
/// Returns currently locked coins, see [crate::traits::VestingAccount::spendable_coins]
|
||||
pub fn try_get_spendable_coins(
|
||||
vesting_account_address: &str,
|
||||
block_time: Option<Timestamp>,
|
||||
@@ -581,6 +646,7 @@ pub fn try_get_spendable_coins(
|
||||
account.spendable_coins(block_time, &env, deps.storage)
|
||||
}
|
||||
|
||||
/// Returns coins that have vested, see [crate::traits::VestingAccount::get_vested_coins]
|
||||
pub fn try_get_vested_coins(
|
||||
vesting_account_address: &str,
|
||||
block_time: Option<Timestamp>,
|
||||
@@ -591,6 +657,7 @@ pub fn try_get_vested_coins(
|
||||
account.get_vested_coins(block_time, &env, deps.storage)
|
||||
}
|
||||
|
||||
/// Returns coins that are vesting, see [crate::traits::VestingAccount::get_vesting_coins]
|
||||
pub fn try_get_vesting_coins(
|
||||
vesting_account_address: &str,
|
||||
block_time: Option<Timestamp>,
|
||||
@@ -601,6 +668,7 @@ pub fn try_get_vesting_coins(
|
||||
account.get_vesting_coins(block_time, &env, deps.storage)
|
||||
}
|
||||
|
||||
/// See [crate::traits::VestingAccount::get_start_time]
|
||||
pub fn try_get_start_time(
|
||||
vesting_account_address: &str,
|
||||
deps: Deps<'_>,
|
||||
@@ -609,6 +677,7 @@ pub fn try_get_start_time(
|
||||
Ok(account.get_start_time())
|
||||
}
|
||||
|
||||
/// See [crate::traits::VestingAccount::get_end_time]
|
||||
pub fn try_get_end_time(
|
||||
vesting_account_address: &str,
|
||||
deps: Deps<'_>,
|
||||
@@ -617,6 +686,7 @@ pub fn try_get_end_time(
|
||||
Ok(account.get_end_time())
|
||||
}
|
||||
|
||||
/// See [crate::traits::VestingAccount::get_original_vesting]
|
||||
pub fn try_get_original_vesting(
|
||||
vesting_account_address: &str,
|
||||
deps: Deps<'_>,
|
||||
@@ -625,6 +695,7 @@ pub fn try_get_original_vesting(
|
||||
Ok(account.get_original_vesting())
|
||||
}
|
||||
|
||||
/// See [crate::traits::VestingAccount::get_delegated_free]
|
||||
pub fn try_get_delegated_free(
|
||||
block_time: Option<Timestamp>,
|
||||
vesting_account_address: &str,
|
||||
@@ -635,6 +706,7 @@ pub fn try_get_delegated_free(
|
||||
account.get_delegated_free(block_time, &env, deps.storage)
|
||||
}
|
||||
|
||||
/// See [crate::traits::VestingAccount::get_delegated_vesting]
|
||||
pub fn try_get_delegated_vesting(
|
||||
block_time: Option<Timestamp>,
|
||||
vesting_account_address: &str,
|
||||
@@ -645,6 +717,7 @@ pub fn try_get_delegated_vesting(
|
||||
account.get_delegated_vesting(block_time, &env, deps.storage)
|
||||
}
|
||||
|
||||
/// Returns timestamps at which delegations were made
|
||||
pub fn try_get_delegation_times(
|
||||
deps: Deps<'_>,
|
||||
vesting_account_address: &str,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#![allow(rustdoc::private_intra_doc_links)]
|
||||
//! Nym vesting contract, providing vesting accounts with ability to stake unvested tokens
|
||||
|
||||
pub mod contract;
|
||||
mod errors;
|
||||
mod queued_migrations;
|
||||
|
||||
@@ -1,17 +1,2 @@
|
||||
// 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())
|
||||
}
|
||||
|
||||
@@ -9,12 +9,13 @@ pub trait VestingAccount {
|
||||
env: &Env,
|
||||
) -> Result<Uint128, ContractError>;
|
||||
|
||||
// locked_coins returns the set of coins that are not spendable (can still be delegated tough) (i.e. locked),
|
||||
// defined as the vesting coins that are not delegated or pledged.
|
||||
//
|
||||
// To get spendable coins of a vesting account, first the total balance must
|
||||
// be retrieved and the locked tokens can be subtracted from the total balance.
|
||||
// Note, the spendable balance can be negative.
|
||||
/// Returns the set of coins that are not spendable (can still be delegated tough) (i.e. locked),
|
||||
/// defined as the vesting coins that are not delegated or pledged.
|
||||
///
|
||||
/// To get spendable coins of a vesting account, first the total balance must
|
||||
/// be retrieved and the locked tokens can be subtracted from the total balance.
|
||||
/// Note, the spendable balance can be negative.
|
||||
/// See [/vesting-contract/struct.Account.html/method.locked_coins] for impl
|
||||
fn locked_coins(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
@@ -22,7 +23,8 @@ pub trait VestingAccount {
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError>;
|
||||
|
||||
// Calculates the total spendable balance that can be sent to other accounts.
|
||||
/// Calculated as current_balance minus [crate::traits::VestingAccount::locked_coins]
|
||||
/// See [/vesting-contract/struct.Account.html/method.spendable_coins] for impl
|
||||
fn spendable_coins(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
@@ -30,12 +32,15 @@ pub trait VestingAccount {
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError>;
|
||||
|
||||
/// See [/vesting-contract/struct.Account.html/method.get_vested_coins] for impl
|
||||
fn get_vested_coins(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError>;
|
||||
|
||||
/// See [/vesting-contract/struct.Account.html/method.get_vesting_coins] for impl
|
||||
fn get_vesting_coins(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
@@ -43,39 +48,52 @@ pub trait VestingAccount {
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError>;
|
||||
|
||||
/// See [/vesting-contract/struct.Account.html/method.get_start_time] for impl
|
||||
fn get_start_time(&self) -> Timestamp;
|
||||
/// See [/vesting-contract/struct.Account.html/method.get_end_time] for impl
|
||||
fn get_end_time(&self) -> Timestamp;
|
||||
|
||||
/// Returns amount of coins set at account creation
|
||||
/// See [/vesting-contract/struct.Account.html/method.get_original_vesting] for impl
|
||||
fn get_original_vesting(&self) -> OriginalVestingResponse;
|
||||
|
||||
/// See [/vesting-contract/struct.Account.html/method.get_delegated_free] for impl
|
||||
fn get_delegated_free(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError>;
|
||||
|
||||
/// See [/vesting-contract/struct.Account.html/method.get_delegated_vesting] for impl
|
||||
fn get_delegated_vesting(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError>;
|
||||
|
||||
/// See [/vesting-contract/struct.Account.html/method.get_pledged_free] for impl
|
||||
fn get_pledged_free(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError>;
|
||||
/// See [/vesting-contract/struct.Account.html/method.get_pledged_vesting] for impl
|
||||
fn get_pledged_vesting(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError>;
|
||||
/// See [/vesting-contract/struct.Account.html/method.transfer_ownership] for impl
|
||||
fn transfer_ownership(
|
||||
&mut self,
|
||||
to_address: &Addr,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError>;
|
||||
/// See [/vesting-contract/struct.Account.html/method.update_staking_address] for impl
|
||||
fn update_staking_address(
|
||||
&mut self,
|
||||
to_address: Option<Addr>,
|
||||
|
||||
@@ -16,13 +16,14 @@ impl VestingAccount for Account {
|
||||
+ self.get_pledged_vesting(None, env, storage)?.amount)
|
||||
}
|
||||
|
||||
/// See [VestingAccount::locked_coins] for documentation.
|
||||
/// Returns 0 in case of underflow. Which is fine, as the amount of pledged and delegated tokens can be larger then vesting_coins due to rewards and vesting periods expiring
|
||||
fn locked_coins(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError> {
|
||||
// Returns 0 in case of underflow. Which is fine, as the amount of pledged and delegated tokens can be larger then vesting_coins due to rewards and vesting periods expiring
|
||||
Ok(Coin {
|
||||
amount: Uint128::new(
|
||||
self.get_vesting_coins(block_time, env, storage)?
|
||||
|
||||
@@ -36,8 +36,8 @@ pub fn populate_vesting_periods(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::contract::execute;
|
||||
use crate::storage::load_account;
|
||||
use crate::contract::*;
|
||||
use crate::storage::*;
|
||||
use crate::support::tests::helpers::{
|
||||
init_contract, vesting_account_mid_fixture, vesting_account_new_fixture, TEST_COIN_DENOM,
|
||||
};
|
||||
@@ -925,4 +925,97 @@ mod tests {
|
||||
// the 50M delegation wasn't a thing here for VESTING tokens either
|
||||
assert_eq!(delegated_vesting.amount, Uint128::zero());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migrate_heights_to_timestamps() {
|
||||
let mut deps = init_contract();
|
||||
let mut env = mock_env();
|
||||
|
||||
let account = vesting_account_new_fixture(&mut deps.storage, &env);
|
||||
let mix_identity = String::from("identity");
|
||||
let mut curr_block = env.block.clone();
|
||||
let mut delegation_blocks = std::iter::from_fn(move || {
|
||||
curr_block.height += 1;
|
||||
curr_block.time = curr_block.time.plus_seconds(5);
|
||||
Some(curr_block.clone())
|
||||
})
|
||||
.take(100)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for block in delegation_blocks.iter() {
|
||||
DELEGATIONS
|
||||
.save(
|
||||
&mut deps.storage,
|
||||
(account.storage_key(), mix_identity.clone(), block.height),
|
||||
&Uint128::new(90_000_000_000),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let delegations = try_get_delegation_times(
|
||||
deps.as_ref(),
|
||||
account.owner_address().as_str(),
|
||||
mix_identity.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
delegations.delegation_timestamps.len(),
|
||||
delegation_blocks.len()
|
||||
);
|
||||
for (heights, delegation_block) in delegations
|
||||
.delegation_timestamps
|
||||
.iter()
|
||||
.zip(delegation_blocks.iter())
|
||||
{
|
||||
assert_eq!(*heights, delegation_block.height);
|
||||
}
|
||||
|
||||
let height_timestamp_map = delegation_blocks
|
||||
.iter()
|
||||
.map(|block| (block.height, block.time.seconds()))
|
||||
.collect();
|
||||
let admin = ADMIN.load(&deps.storage).unwrap();
|
||||
try_migrate_heights_to_timestamps(
|
||||
account.storage_key(),
|
||||
mix_identity.clone(),
|
||||
height_timestamp_map,
|
||||
mock_info(&admin, &[]),
|
||||
deps.as_mut(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Some new delegation appears during migration
|
||||
env.block.height += 200;
|
||||
env.block.time = env.block.time.plus_seconds(1000);
|
||||
delegation_blocks.push(env.block.clone());
|
||||
account
|
||||
.try_delegate_to_mixnode(
|
||||
String::from("identity"),
|
||||
Coin {
|
||||
amount: Uint128::new(90_000_000_000),
|
||||
denom: TEST_COIN_DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let delegations = try_get_delegation_times(
|
||||
deps.as_ref(),
|
||||
account.owner_address().as_str(),
|
||||
mix_identity.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
delegations.delegation_timestamps.len(),
|
||||
delegation_blocks.len()
|
||||
);
|
||||
for (timestamp, delegation_block) in delegations
|
||||
.delegation_timestamps
|
||||
.iter()
|
||||
.zip(delegation_blocks.iter())
|
||||
{
|
||||
assert_eq!(*timestamp, delegation_block.time.seconds());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Examples
|
||||
|
||||
This directory contains examples of using the libraries in this repo:
|
||||
|
||||
### CLI
|
||||
|
||||
#### CLI Commands
|
||||
|
||||
- [Use an account public key to verify a signature](./cli/commands/verify-signature)
|
||||
+388
-388
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "example-verify-signature"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
nym-cli-commands = { git = "https://github.com/nymtech/nym", branch = "develop" }
|
||||
@@ -0,0 +1,38 @@
|
||||
# Example code to verify a signature
|
||||
|
||||
This is an example app that shows how to verify a signature signed by an account key.
|
||||
|
||||
Inputs to the app are:
|
||||
|
||||
- **signature** - bytes represented as a hex string
|
||||
- **public key** - in JSON format (you can query any Cosmos chain for account's public key as JSON, however it will need to have sent a signed transaction to the chain for this to be present)
|
||||
- **message** - the string message to verify
|
||||
|
||||
## Running locally
|
||||
|
||||
Run the example by changning to this directory and running:
|
||||
|
||||
```
|
||||
cargo run
|
||||
```
|
||||
|
||||
And you should see the output:
|
||||
|
||||
```
|
||||
Nym signature verification example
|
||||
|
||||
|
||||
public key: {"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A4FdhUMasPmNhRZjtpKlmjNbq7EEUgPxfdI+E3vSajvc"}
|
||||
signature: E3AA5AC0DA1B7DEBB7808000F719D8ACB9A0BE10AFA2756A788516268EB246A1257EC1097C5E364EF916145B01641DEDFE955994CB340BDAFA99A65BCA3F6F28
|
||||
message: test 1234
|
||||
|
||||
|
||||
Verify the correct message:
|
||||
|
||||
SUCCESS ✅ signature is valid
|
||||
|
||||
|
||||
Verify another message:
|
||||
|
||||
FAILURE ❌ signature is not valid: signature error
|
||||
```
|
||||
@@ -0,0 +1,43 @@
|
||||
use nym_cli_commands::validator::signature::helpers::secp256k1_verify_with_public_key_json;
|
||||
|
||||
fn main() {
|
||||
println!("\nNym signature verification example\n\n");
|
||||
|
||||
// the public key in JSON format (because Cosmos supports secp256k1 and ed25519 - NB: the helper only supports secp256k1)
|
||||
let public_key_as_json = r#"{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A4FdhUMasPmNhRZjtpKlmjNbq7EEUgPxfdI+E3vSajvc"}"#;
|
||||
|
||||
// the signature as a string of hex characters to represent the bytes in the signature
|
||||
let signature_as_hex = "E3AA5AC0DA1B7DEBB7808000F719D8ACB9A0BE10AFA2756A788516268EB246A1257EC1097C5E364EF916145B01641DEDFE955994CB340BDAFA99A65BCA3F6F28";
|
||||
|
||||
// the original message as a string to verify
|
||||
let message = "test 1234".to_string();
|
||||
|
||||
println!("public key: {}", &public_key_as_json);
|
||||
println!("signature: {}", &signature_as_hex);
|
||||
println!("message: {}", &message);
|
||||
|
||||
println!();
|
||||
|
||||
// this will pass, because the signature was signed for this message
|
||||
println!("\nVerify the correct message:\n");
|
||||
do_verify(
|
||||
public_key_as_json.to_string(),
|
||||
signature_as_hex.to_string(),
|
||||
message,
|
||||
);
|
||||
|
||||
// this will fail, because the signature is for another message
|
||||
println!("\n\nVerify another message:\n");
|
||||
do_verify(
|
||||
public_key_as_json.to_string(),
|
||||
signature_as_hex.to_string(),
|
||||
"another message that will fail".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
fn do_verify(public_key_as_json: String, signature_as_hex: String, message: String) {
|
||||
match secp256k1_verify_with_public_key_json(public_key_as_json, signature_as_hex, message) {
|
||||
Ok(()) => println!("SUCCESS ✅ signature is valid"),
|
||||
Err(e) => println!("FAILURE ❌ signature is not valid: {}", e),
|
||||
}
|
||||
}
|
||||
Generated
+7
-4
@@ -629,6 +629,8 @@ dependencies = [
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"sled",
|
||||
"tap",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"topology",
|
||||
"url",
|
||||
@@ -5227,6 +5229,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"nymsphinx-addressing",
|
||||
"ordered-buffer",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5943,18 +5946,18 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.31"
|
||||
version = "1.0.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
|
||||
checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.31"
|
||||
version = "1.0.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
|
||||
checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user