Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dd83631be1 | |||
| 405ca63c7b | |||
| 7f17522666 | |||
| ba158f822d | |||
| 0f147750ae | |||
| 60d17389d0 | |||
| 94d70e47fc |
Generated
+91
-2
@@ -2384,6 +2384,17 @@ version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0"
|
||||
|
||||
[[package]]
|
||||
name = "hidapi"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d26e1151deaab68f34fbfd16d491a2a0170cf98d69d3efa23873b567a4199e1"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.11.0"
|
||||
@@ -2815,6 +2826,54 @@ version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "ledger"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bip32",
|
||||
"k256",
|
||||
"ledger-transport",
|
||||
"ledger-transport-hid",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ledger-apdu"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe435806c197dfeaa5efcded5e623c4b8230fd28fdf1e91e7a86e40ef2acbf90"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"no-std-compat",
|
||||
"snafu 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ledger-transport"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1117f2143d92c157197785bf57711d7b02f2cfa101e162f8ca7900fb7f976321"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"ledger-apdu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ledger-transport-hid"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45ba81a1f5f24396b37211478aff7fbcd605dd4544df8dbed07b9da3c2057aee"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"cfg-if 1.0.0",
|
||||
"hex",
|
||||
"hidapi",
|
||||
"ledger-transport",
|
||||
"libc",
|
||||
"log",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.132"
|
||||
@@ -3105,6 +3164,12 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "no-std-compat"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.1"
|
||||
@@ -3423,7 +3488,7 @@ dependencies = [
|
||||
"proxy-helpers",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"snafu",
|
||||
"snafu 0.6.10",
|
||||
"socks5-requests",
|
||||
"task",
|
||||
"tokio",
|
||||
@@ -3444,6 +3509,7 @@ dependencies = [
|
||||
"cosmwasm-std",
|
||||
"eyre",
|
||||
"itertools",
|
||||
"ledger",
|
||||
"log",
|
||||
"mixnet-contract-common",
|
||||
"reqwest",
|
||||
@@ -5338,7 +5404,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
"snafu-derive",
|
||||
"snafu-derive 0.6.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5177903bf45656592d9eb5c0e22f408fc023aae51dbe2088889b71633ba451f2"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
"snafu-derive 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5352,6 +5428,18 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu-derive"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "410b26ed97440d90ced3e2488c868d56a86e2064f5d7d6f417909b286afe25e5"
|
||||
dependencies = [
|
||||
"heck 0.4.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.7"
|
||||
@@ -6610,6 +6698,7 @@ dependencies = [
|
||||
"flate2",
|
||||
"futures",
|
||||
"itertools",
|
||||
"ledger",
|
||||
"log",
|
||||
"mixnet-contract-common",
|
||||
"multisig-contract-common",
|
||||
|
||||
@@ -40,6 +40,7 @@ members = [
|
||||
"common/crypto/dkg",
|
||||
"common/execute",
|
||||
"common/inclusion-probability",
|
||||
"common/ledger",
|
||||
"common/mixnode-common",
|
||||
"common/network-defaults",
|
||||
"common/nonexhaustive-delayqueue",
|
||||
|
||||
@@ -42,6 +42,7 @@ sha2 = { version = "0.9.5", optional = true }
|
||||
itertools = { version = "0.10", optional = true }
|
||||
cosmwasm-std = { version = "1.0.0", optional = true }
|
||||
execute = { path = "../../execute" }
|
||||
ledger = { path = "../../ledger" }
|
||||
|
||||
[dev-dependencies]
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
@@ -19,8 +19,11 @@ use validator_api_requests::models::{MixNodeBondAnnotated, UptimeResponse};
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use crate::nymd::{
|
||||
self, error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient,
|
||||
WalletNymdClient,
|
||||
};
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use ledger::CosmosLedger;
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use mixnet_contract_common::{
|
||||
mixnode::DelegationEvent, ContractStateParams, Delegation, IdentityKey, Interval,
|
||||
@@ -28,7 +31,7 @@ use mixnet_contract_common::{
|
||||
RewardedSetUpdateDetails,
|
||||
};
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use network_defaults::NymNetworkDetails;
|
||||
use network_defaults::{NymNetworkDetails, COSMOS_DERIVATION_PATH};
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
@@ -167,6 +170,51 @@ impl Client<SigningNymdClient> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
impl Client<WalletNymdClient> {
|
||||
pub fn new_ledger(config: Config) -> Result<Client<WalletNymdClient>, ValidatorClientError> {
|
||||
let validator_api_client = validator_api::Client::new(config.api_url.clone());
|
||||
let signer = CosmosLedger::new(
|
||||
COSMOS_DERIVATION_PATH.parse().unwrap(),
|
||||
config
|
||||
.nymd_config
|
||||
.chain_details
|
||||
.bech32_account_prefix
|
||||
.clone(),
|
||||
)?;
|
||||
let nymd_client = NymdClient::connect_with_ledger(
|
||||
config.nymd_config.clone(),
|
||||
config.nymd_url.as_str(),
|
||||
signer,
|
||||
None,
|
||||
)?;
|
||||
|
||||
Ok(Client {
|
||||
mnemonic: None,
|
||||
mixnode_page_limit: config.mixnode_page_limit,
|
||||
gateway_page_limit: config.gateway_page_limit,
|
||||
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
|
||||
rewarded_set_page_limit: config.rewarded_set_page_limit,
|
||||
validator_api: validator_api_client,
|
||||
nymd: nymd_client,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn change_nymd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> {
|
||||
self.nymd = NymdClient::connect_with_ledger(
|
||||
self.nymd.current_config().clone(),
|
||||
new_endpoint.as_ref(),
|
||||
self.nymd.ledger_signer(),
|
||||
None,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_nymd_simulated_gas_multiplier(&mut self, multiplier: f32) {
|
||||
self.nymd.set_simulated_gas_multiplier(multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
impl Client<QueryNymdClient> {
|
||||
pub fn new_query(config: Config) -> Result<Client<QueryNymdClient>, ValidatorClientError> {
|
||||
|
||||
@@ -19,6 +19,10 @@ pub enum ValidatorClientError {
|
||||
#[error("There was an issue with the Nymd client - {0}")]
|
||||
NymdError(#[from] crate::nymd::error::NymdError),
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
#[error("There was an issue with the Ledger client - {0}")]
|
||||
LedgerError(#[from] ledger::error::LedgerError),
|
||||
|
||||
#[error("No validator API url has been provided")]
|
||||
NoAPIUrlAvailable,
|
||||
}
|
||||
|
||||
@@ -262,6 +262,7 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
let broadcasted = CosmWasmClient::broadcast_tx_sync(self, tx).await?;
|
||||
|
||||
if broadcasted.code.is_err() {
|
||||
println!("Error {:?}", broadcasted);
|
||||
let code_val = broadcasted.code.value();
|
||||
return Err(NymdError::BroadcastTxErrorDeliverTx {
|
||||
hash: broadcasted.hash,
|
||||
|
||||
@@ -5,13 +5,19 @@ use crate::nymd::error::NymdError;
|
||||
use crate::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
use crate::nymd::GasPrice;
|
||||
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl};
|
||||
use ledger::CosmosLedger;
|
||||
use std::convert::TryInto;
|
||||
use std::time::Duration;
|
||||
|
||||
pub mod client;
|
||||
mod helpers;
|
||||
pub mod logs;
|
||||
pub mod signing_client;
|
||||
pub mod types;
|
||||
pub mod wallet_client;
|
||||
|
||||
pub(crate) const DEFAULT_BROADCAST_POLLING_RATE: Duration = Duration::from_secs(4);
|
||||
pub(crate) const DEFAULT_BROADCAST_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
|
||||
pub fn connect<U>(endpoint: U) -> Result<HttpClient, NymdError>
|
||||
where
|
||||
@@ -31,3 +37,14 @@ where
|
||||
{
|
||||
signing_client::Client::connect_with_signer(endpoint, signer, gas_price)
|
||||
}
|
||||
|
||||
pub fn connect_with_ledger<U: Clone>(
|
||||
endpoint: U,
|
||||
signer: CosmosLedger,
|
||||
gas_price: GasPrice,
|
||||
) -> Result<wallet_client::Client, NymdError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
wallet_client::Client::connect_with_ledger(endpoint, signer, gas_price)
|
||||
}
|
||||
|
||||
@@ -5,22 +5,23 @@ use crate::nymd::cosmwasm_client::client::CosmWasmClient;
|
||||
use crate::nymd::cosmwasm_client::helpers::{compress_wasm_code, CheckResponse};
|
||||
use crate::nymd::cosmwasm_client::logs::{self, parse_raw_logs};
|
||||
use crate::nymd::cosmwasm_client::types::*;
|
||||
use crate::nymd::cosmwasm_client::{DEFAULT_BROADCAST_POLLING_RATE, DEFAULT_BROADCAST_TIMEOUT};
|
||||
use crate::nymd::error::NymdError;
|
||||
use crate::nymd::fee::{Fee, DEFAULT_SIMULATED_GAS_MULTIPLIER};
|
||||
use crate::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
use crate::nymd::{Coin, GasAdjustable, GasPrice, TxResponse};
|
||||
use crate::nymd::{Coin, GasAdjustable, GasPrice, MinimalSigningCosmWasmClient, TxResponse};
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::bank::MsgSend;
|
||||
use cosmrs::distribution::MsgWithdrawDelegatorReward;
|
||||
use cosmrs::feegrant::{
|
||||
AllowedMsgAllowance, BasicAllowance, MsgGrantAllowance, MsgRevokeAllowance,
|
||||
};
|
||||
use cosmrs::proto::cosmos::tx::signing::v1beta1::SignMode;
|
||||
use cosmrs::rpc::endpoint::broadcast;
|
||||
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
|
||||
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
|
||||
use cosmrs::tx::{self, Msg, SignDoc, SignerInfo};
|
||||
use cosmrs::{cosmwasm, rpc, AccountId, Any, Tx};
|
||||
use ledger::CosmosLedger;
|
||||
use log::debug;
|
||||
use serde::Serialize;
|
||||
use sha2::Digest;
|
||||
@@ -28,40 +29,16 @@ use sha2::Sha256;
|
||||
use std::convert::TryInto;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
const DEFAULT_BROADCAST_POLLING_RATE: Duration = Duration::from_secs(4);
|
||||
const DEFAULT_BROADCAST_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
|
||||
fn empty_fee() -> tx::Fee {
|
||||
tx::Fee {
|
||||
amount: vec![],
|
||||
gas_limit: Default::default(),
|
||||
payer: None,
|
||||
granter: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn single_unspecified_signer_auth(
|
||||
public_key: Option<tx::SignerPublicKey>,
|
||||
sequence_number: tx::SequenceNumber,
|
||||
) -> tx::AuthInfo {
|
||||
tx::SignerInfo {
|
||||
public_key,
|
||||
mode_info: tx::ModeInfo::Single(tx::mode_info::Single {
|
||||
mode: SignMode::Unspecified,
|
||||
}),
|
||||
sequence: sequence_number,
|
||||
}
|
||||
.auth_info(empty_fee())
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
pub trait SigningCosmWasmClient: MinimalSigningCosmWasmClient {
|
||||
fn signer(&self) -> &DirectSecp256k1HdWallet;
|
||||
|
||||
fn gas_price(&self) -> &GasPrice;
|
||||
|
||||
fn signer_public_key(&self, signer_address: &AccountId) -> Option<tx::SignerPublicKey> {
|
||||
let signer_accounts = self.signer().try_derive_accounts().ok()?;
|
||||
let signer_accounts = SigningCosmWasmClient::signer(self)
|
||||
.try_derive_accounts()
|
||||
.ok()?;
|
||||
let account_from_signer = signer_accounts
|
||||
.iter()
|
||||
.find(|account| &account.address == signer_address)?;
|
||||
@@ -69,33 +46,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
Some(public_key.into())
|
||||
}
|
||||
|
||||
async fn simulate(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Any>,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<SimulateResponse, NymdError> {
|
||||
let public_key = self.signer_public_key(signer_address);
|
||||
let sequence_response = self.get_sequence(signer_address).await?;
|
||||
|
||||
let partial_tx = Tx {
|
||||
body: tx::Body::new(messages, memo, 0u32),
|
||||
auth_info: single_unspecified_signer_auth(public_key, sequence_response.sequence),
|
||||
signatures: vec![Vec::new()],
|
||||
};
|
||||
self.query_simulate(Some(partial_tx), Vec::new()).await
|
||||
|
||||
// for completion sake, once we're able to transition into using `tx_bytes`,
|
||||
// we might want to use something like this instead:
|
||||
// let tx_raw: tx::Raw = cosmrs::proto::cosmos::tx::v1beta1::TxRaw {
|
||||
// body_bytes: partial_tx.body.into_bytes().unwrap(),
|
||||
// auth_info_bytes: partial_tx.auth_info.into_bytes().unwrap(),
|
||||
// signatures: partial_tx.signatures,
|
||||
// }
|
||||
// .into();
|
||||
// self.query_simulate(None, tx_raw.to_bytes().unwrap()).await
|
||||
}
|
||||
|
||||
async fn upload(
|
||||
&self,
|
||||
sender_address: &AccountId,
|
||||
@@ -541,50 +491,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.check_response()
|
||||
}
|
||||
|
||||
// in this particular case we cannot generalise the argument to `&str` due to lifetime constraints
|
||||
#[allow(clippy::ptr_arg)]
|
||||
async fn determine_transaction_fee(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: &[Any],
|
||||
fee: Fee,
|
||||
memo: &String,
|
||||
) -> Result<tx::Fee, NymdError> {
|
||||
let auto_fee = |multiplier: Option<f32>| async move {
|
||||
debug!("Trying to simulate gas costs...");
|
||||
// from what I've seen in manual testing, gas estimation does not exist if transaction
|
||||
// fails to get executed (for example if you send 'BondMixnode" with invalid signature)
|
||||
let gas_estimation = self
|
||||
.simulate(signer_address, messages.to_vec(), memo.clone())
|
||||
.await?
|
||||
.gas_info
|
||||
.ok_or(NymdError::GasEstimationFailure)?
|
||||
.gas_used;
|
||||
|
||||
let multiplier = multiplier.unwrap_or(DEFAULT_SIMULATED_GAS_MULTIPLIER);
|
||||
let gas = gas_estimation.adjust_gas(multiplier);
|
||||
|
||||
debug!("Gas estimation: {}", gas_estimation);
|
||||
debug!("Multiplying the estimation by {}", multiplier);
|
||||
debug!("Final gas limit used: {}", gas);
|
||||
|
||||
let fee = self.gas_price() * gas;
|
||||
Ok::<tx::Fee, NymdError>(tx::Fee::from_amount_and_gas(fee, gas))
|
||||
};
|
||||
let fee = match fee {
|
||||
Fee::Manual(fee) => fee,
|
||||
Fee::Auto(multiplier) => auto_fee(multiplier).await?,
|
||||
Fee::PayerGranterAuto(auto_feegrant) => {
|
||||
let mut fee = auto_fee(auto_feegrant.gas_adjustment).await?;
|
||||
fee.payer = auto_feegrant.payer;
|
||||
fee.granter = auto_feegrant.granter;
|
||||
fee
|
||||
}
|
||||
};
|
||||
debug!("Fee used for the transaction: {:?}", fee);
|
||||
Ok(fee)
|
||||
}
|
||||
|
||||
/// Broadcast a transaction, returning immediately.
|
||||
async fn sign_and_broadcast_async(
|
||||
&self,
|
||||
@@ -597,7 +503,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
let fee = self
|
||||
.determine_transaction_fee(signer_address, &messages, fee, &memo)
|
||||
.await?;
|
||||
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
|
||||
let tx_raw = SigningCosmWasmClient::sign(self, signer_address, messages, fee, memo).await?;
|
||||
let tx_bytes = tx_raw
|
||||
.to_bytes()
|
||||
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
|
||||
@@ -617,7 +523,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
let fee = self
|
||||
.determine_transaction_fee(signer_address, &messages, fee, &memo)
|
||||
.await?;
|
||||
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
|
||||
let tx_raw = SigningCosmWasmClient::sign(self, signer_address, messages, fee, memo).await?;
|
||||
let tx_bytes = tx_raw
|
||||
.to_bytes()
|
||||
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
|
||||
@@ -638,7 +544,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.determine_transaction_fee(signer_address, &messages, fee, &memo)
|
||||
.await?;
|
||||
|
||||
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
|
||||
let tx_raw = SigningCosmWasmClient::sign(self, signer_address, messages, fee, memo).await?;
|
||||
let tx_bytes = tx_raw
|
||||
.to_bytes()
|
||||
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
|
||||
@@ -659,7 +565,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.determine_transaction_fee(signer_address, &messages, fee, &memo)
|
||||
.await?;
|
||||
|
||||
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
|
||||
let tx_raw = SigningCosmWasmClient::sign(self, signer_address, messages, fee, memo).await?;
|
||||
let tx_bytes = tx_raw
|
||||
.to_bytes()
|
||||
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
|
||||
@@ -675,7 +581,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
signer_data: SignerData,
|
||||
) -> Result<tx::Raw, NymdError> {
|
||||
let signer_accounts = self.signer().try_derive_accounts()?;
|
||||
let signer_accounts = SigningCosmWasmClient::signer(self).try_derive_accounts()?;
|
||||
let account_from_signer = signer_accounts
|
||||
.iter()
|
||||
.find(|account| &account.address == signer_address)
|
||||
@@ -701,8 +607,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
)
|
||||
.map_err(|_| NymdError::SigningFailure)?;
|
||||
|
||||
self.signer()
|
||||
.sign_direct_with_account(account_from_signer, sign_doc)
|
||||
SigningCosmWasmClient::signer(self).sign_direct_with_account(account_from_signer, sign_doc)
|
||||
}
|
||||
|
||||
async fn sign(
|
||||
@@ -786,7 +691,16 @@ impl CosmWasmClient for Client {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl MinimalSigningCosmWasmClient for Client {
|
||||
fn signer(&self) -> CosmosLedger {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn gas_price(&self) -> &GasPrice {
|
||||
&self.gas_price
|
||||
}
|
||||
}
|
||||
|
||||
impl SigningCosmWasmClient for Client {
|
||||
fn signer(&self) -> &DirectSecp256k1HdWallet {
|
||||
&self.signer
|
||||
|
||||
@@ -0,0 +1,490 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nymd::cosmwasm_client::client::CosmWasmClient;
|
||||
use crate::nymd::cosmwasm_client::helpers::CheckResponse;
|
||||
use crate::nymd::cosmwasm_client::logs::{self, parse_raw_logs};
|
||||
use crate::nymd::cosmwasm_client::types::*;
|
||||
use crate::nymd::cosmwasm_client::HttpClient;
|
||||
use crate::nymd::cosmwasm_client::{DEFAULT_BROADCAST_POLLING_RATE, DEFAULT_BROADCAST_TIMEOUT};
|
||||
use crate::nymd::fee::GasAdjustable;
|
||||
use crate::nymd::wallet_client::rpc::SimpleRequest;
|
||||
use crate::nymd::DEFAULT_SIMULATED_GAS_MULTIPLIER;
|
||||
use crate::nymd::{
|
||||
DirectSecp256k1HdWallet, GasPrice, HttpClientUrl, NymdError, SigningCosmWasmClient,
|
||||
SimulateResponse, TendermintRpcError, TxResponse,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::proto::cosmos::tx::signing::v1beta1::SignMode;
|
||||
use cosmrs::rpc;
|
||||
use cosmrs::tx::mode_info::Single;
|
||||
use cosmrs::tx::{self, Gas, ModeInfo, Msg, MsgProto, SignDoc, SignerInfo};
|
||||
use cosmrs::{AccountId, Any};
|
||||
use ledger::CosmosLedger;
|
||||
use log::{debug, info};
|
||||
use mixnet_contract_common::ExecuteMsg;
|
||||
use serde::Serialize;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
fn single_unspecified_signer_auth(
|
||||
public_key: Option<tx::SignerPublicKey>,
|
||||
sequence_number: tx::SequenceNumber,
|
||||
) -> tx::AuthInfo {
|
||||
tx::SignerInfo {
|
||||
public_key,
|
||||
mode_info: tx::ModeInfo::Single(tx::mode_info::Single {
|
||||
mode: SignMode::Unspecified,
|
||||
}),
|
||||
sequence: sequence_number,
|
||||
}
|
||||
.auth_info(empty_fee())
|
||||
}
|
||||
|
||||
fn empty_fee() -> tx::Fee {
|
||||
tx::Fee {
|
||||
amount: vec![],
|
||||
gas_limit: Default::default(),
|
||||
payer: None,
|
||||
granter: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Client {
|
||||
rpc_client: HttpClient,
|
||||
signer: CosmosLedger,
|
||||
gas_price: GasPrice,
|
||||
|
||||
broadcast_polling_rate: Duration,
|
||||
broadcast_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn connect_with_ledger<U: Clone>(
|
||||
endpoint: U,
|
||||
signer: CosmosLedger,
|
||||
gas_price: GasPrice,
|
||||
) -> Result<Self, NymdError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
let rpc_client = HttpClient::new(endpoint)?;
|
||||
Ok(Client {
|
||||
rpc_client,
|
||||
signer,
|
||||
gas_price,
|
||||
broadcast_polling_rate: DEFAULT_BROADCAST_POLLING_RATE,
|
||||
broadcast_timeout: DEFAULT_BROADCAST_TIMEOUT,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_broadcast_polling_rate(&mut self, broadcast_polling_rate: Duration) {
|
||||
self.broadcast_polling_rate = broadcast_polling_rate
|
||||
}
|
||||
|
||||
pub fn set_broadcast_timeout(&mut self, broadcast_timeout: Duration) {
|
||||
self.broadcast_timeout = broadcast_timeout
|
||||
}
|
||||
|
||||
pub fn signer(&self) -> CosmosLedger {
|
||||
self.signer.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl rpc::Client for Client {
|
||||
async fn perform<R>(&self, request: R) -> Result<R::Response, rpc::Error>
|
||||
where
|
||||
R: SimpleRequest,
|
||||
{
|
||||
self.rpc_client.perform(request).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CosmWasmClient for Client {
|
||||
fn broadcast_polling_rate(&self) -> Duration {
|
||||
self.broadcast_polling_rate
|
||||
}
|
||||
|
||||
fn broadcast_timeout(&self) -> Duration {
|
||||
self.broadcast_timeout
|
||||
}
|
||||
}
|
||||
|
||||
impl MinimalSigningCosmWasmClient for Client {
|
||||
fn signer(&self) -> CosmosLedger {
|
||||
self.signer.clone()
|
||||
}
|
||||
|
||||
fn gas_price(&self) -> &GasPrice {
|
||||
&self.gas_price
|
||||
}
|
||||
}
|
||||
|
||||
impl SigningCosmWasmClient for Client {
|
||||
fn signer(&self) -> &DirectSecp256k1HdWallet {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn gas_price(&self) -> &GasPrice {
|
||||
&self.gas_price
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Coin {
|
||||
amount: String,
|
||||
denom: String,
|
||||
}
|
||||
|
||||
impl From<crate::nymd::Coin> for Coin {
|
||||
fn from(coin: crate::nymd::Coin) -> Self {
|
||||
Coin {
|
||||
amount: coin.amount.to_string(),
|
||||
denom: coin.denom.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cosmrs::Coin> for Coin {
|
||||
fn from(coin: cosmrs::Coin) -> Self {
|
||||
let nymd_coin: crate::nymd::Coin = coin.into();
|
||||
nymd_coin.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Fee {
|
||||
amount: Vec<Coin>,
|
||||
gas: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct SendValue {
|
||||
amount: Vec<Coin>,
|
||||
from_address: String,
|
||||
to_address: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct MsgSend {
|
||||
#[serde(rename = "type")]
|
||||
type_url: String,
|
||||
value: SendValue,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct SendTransaction {
|
||||
account_number: String,
|
||||
chain_id: String,
|
||||
fee: Fee,
|
||||
memo: String,
|
||||
msgs: Vec<MsgSend>,
|
||||
sequence: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ExecuteContractValue {
|
||||
contract: String,
|
||||
funds: Vec<Coin>,
|
||||
msg: ExecuteMsg,
|
||||
sender: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct MsgExecuteContract {
|
||||
#[serde(rename = "type")]
|
||||
type_url: String,
|
||||
value: ExecuteContractValue,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ExecuteContractTransaction {
|
||||
account_number: String,
|
||||
chain_id: String,
|
||||
fee: Fee,
|
||||
memo: String,
|
||||
msgs: Vec<MsgExecuteContract>,
|
||||
sequence: String,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait MinimalSigningCosmWasmClient: CosmWasmClient {
|
||||
fn signer(&self) -> CosmosLedger;
|
||||
|
||||
fn gas_price(&self) -> &GasPrice;
|
||||
|
||||
fn signer_public_key(&self, signer_address: &AccountId) -> Option<tx::SignerPublicKey> {
|
||||
let response = self.signer().get_addr_secp265k1(false).ok()?;
|
||||
if response.address == signer_address.to_string() {
|
||||
let verifying_key: cosmrs::crypto::secp256k1::VerifyingKey = response.public_key.into();
|
||||
let cosmrs_public_key: cosmrs::crypto::PublicKey = verifying_key.into();
|
||||
Some(cosmrs_public_key.into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
async fn simulate(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Any>,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<SimulateResponse, NymdError> {
|
||||
let public_key = self.signer_public_key(signer_address);
|
||||
let sequence_response = self.get_sequence(signer_address).await?;
|
||||
|
||||
let partial_tx = cosmrs::Tx {
|
||||
body: tx::Body::new(messages, memo, 0u32),
|
||||
auth_info: single_unspecified_signer_auth(public_key, sequence_response.sequence),
|
||||
signatures: vec![Vec::new()],
|
||||
};
|
||||
self.query_simulate(Some(partial_tx), Vec::new()).await
|
||||
|
||||
// for completion sake, once we're able to transition into using `tx_bytes`,
|
||||
// we might want to use something like this instead:
|
||||
// let tx_raw: tx::Raw = cosmrs::proto::cosmos::tx::v1beta1::TxRaw {
|
||||
// body_bytes: partial_tx.body.into_bytes().unwrap(),
|
||||
// auth_info_bytes: partial_tx.auth_info.into_bytes().unwrap(),
|
||||
// signatures: partial_tx.signatures,
|
||||
// }
|
||||
// .into();
|
||||
// self.query_simulate(None, tx_raw.to_bytes().unwrap()).await
|
||||
}
|
||||
|
||||
// in this particular case we cannot generalise the argument to `&str` due to lifetime constraints
|
||||
#[allow(clippy::ptr_arg)]
|
||||
async fn determine_transaction_fee(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: &[Any],
|
||||
fee: crate::nymd::fee::Fee,
|
||||
memo: &String,
|
||||
) -> Result<tx::Fee, NymdError> {
|
||||
let auto_fee = |multiplier: Option<f32>| async move {
|
||||
debug!("Trying to simulate gas costs...");
|
||||
// from what I've seen in manual testing, gas estimation does not exist if transaction
|
||||
// fails to get executed (for example if you send 'BondMixnode" with invalid signature)
|
||||
let gas_estimation = self
|
||||
.simulate(signer_address, messages.to_vec(), memo.clone())
|
||||
.await?
|
||||
.gas_info
|
||||
.ok_or(NymdError::GasEstimationFailure)?
|
||||
.gas_used;
|
||||
|
||||
let multiplier = multiplier.unwrap_or(DEFAULT_SIMULATED_GAS_MULTIPLIER);
|
||||
let gas = gas_estimation.adjust_gas(multiplier);
|
||||
|
||||
debug!("Gas estimation: {}", gas_estimation);
|
||||
debug!("Multiplying the estimation by {}", multiplier);
|
||||
debug!("Final gas limit used: {}", gas);
|
||||
|
||||
let fee = self.gas_price() * gas;
|
||||
Ok::<tx::Fee, NymdError>(tx::Fee::from_amount_and_gas(fee, gas))
|
||||
};
|
||||
let fee = match fee {
|
||||
crate::nymd::fee::Fee::Manual(fee) => fee,
|
||||
crate::nymd::fee::Fee::Auto(multiplier) => auto_fee(multiplier).await?,
|
||||
crate::nymd::fee::Fee::PayerGranterAuto(auto_feegrant) => {
|
||||
let mut fee = auto_fee(auto_feegrant.gas_adjustment).await?;
|
||||
fee.payer = auto_feegrant.payer;
|
||||
fee.granter = auto_feegrant.granter;
|
||||
fee
|
||||
}
|
||||
};
|
||||
debug!("Fee used for the transaction: {:?}", fee);
|
||||
Ok(fee)
|
||||
}
|
||||
|
||||
fn sign(
|
||||
&self,
|
||||
messages: String,
|
||||
cosmrs_messages: Vec<Any>,
|
||||
fee: tx::Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
signer_data: SignerData,
|
||||
) -> Result<tx::Raw, NymdError> {
|
||||
let signature = self
|
||||
.signer()
|
||||
.sign_secp265k1(messages)
|
||||
.expect("Could not sign")
|
||||
.signature;
|
||||
let response = self.signer().get_addr_secp265k1(false)?;
|
||||
let verifying_key: cosmrs::crypto::secp256k1::VerifyingKey = response.public_key.into();
|
||||
let cosmrs_public_key: cosmrs::crypto::PublicKey = verifying_key.into();
|
||||
|
||||
// TODO: WTF HOW IS TIMEOUT_HEIGHT SUPPOSED TO GET DETERMINED?
|
||||
// IT DOESNT EXIST IN COSMJS!!
|
||||
// try to set to 0
|
||||
let timeout_height = 0u32;
|
||||
|
||||
let tx_body = tx::Body::new(cosmrs_messages, memo, timeout_height);
|
||||
let mut signer_info =
|
||||
SignerInfo::single_direct(Some(cosmrs_public_key), signer_data.sequence);
|
||||
signer_info.mode_info = ModeInfo::Single(Single {
|
||||
mode: SignMode::LegacyAminoJson,
|
||||
});
|
||||
let auth_info = signer_info.auth_info(fee);
|
||||
|
||||
// ideally I'd prefer to have the entire error put into the NymdError::SigningFailure
|
||||
// but I'm super hesitant to trying to downcast the eyre::Report to cosmrs::error::Error
|
||||
let sign_doc = SignDoc::new(
|
||||
&tx_body,
|
||||
&auth_info,
|
||||
&signer_data.chain_id,
|
||||
signer_data.account_number,
|
||||
)
|
||||
.map_err(|_| NymdError::SigningFailure)?;
|
||||
|
||||
Ok(cosmrs::proto::cosmos::tx::v1beta1::TxRaw {
|
||||
body_bytes: sign_doc.body_bytes,
|
||||
auth_info_bytes: sign_doc.auth_info_bytes,
|
||||
signatures: vec![signature.as_ref().to_vec()],
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
async fn send_tokens(
|
||||
&self,
|
||||
sender_address: &AccountId,
|
||||
recipient_address: &AccountId,
|
||||
amount: Vec<crate::nymd::Coin>,
|
||||
fee: crate::nymd::fee::Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<TxResponse, NymdError> {
|
||||
let cosmrs_msg = cosmrs::bank::MsgSend {
|
||||
from_address: sender_address.clone(),
|
||||
to_address: recipient_address.clone(),
|
||||
amount: amount.iter().cloned().map(Into::into).collect(),
|
||||
}
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?;
|
||||
let memo = memo.into();
|
||||
let fee = self
|
||||
.determine_transaction_fee(sender_address, &vec![cosmrs_msg.clone()], fee, &memo)
|
||||
.await?;
|
||||
|
||||
let response = self.signer().get_addr_secp265k1(false)?;
|
||||
let sequence_response = self
|
||||
.get_sequence(&AccountId::from_str(&response.address).unwrap())
|
||||
.await?;
|
||||
let chain_id = self.get_chain_id().await?;
|
||||
|
||||
let signer_data = SignerData {
|
||||
account_number: sequence_response.account_number,
|
||||
sequence: sequence_response.sequence,
|
||||
chain_id,
|
||||
};
|
||||
|
||||
let send_msg = MsgSend {
|
||||
type_url: String::from("cosmos-sdk/MsgSend"),
|
||||
value: SendValue {
|
||||
from_address: sender_address.to_string(),
|
||||
to_address: recipient_address.to_string(),
|
||||
amount: amount.iter().cloned().map(Into::into).collect(),
|
||||
},
|
||||
};
|
||||
let tx = SendTransaction {
|
||||
account_number: signer_data.account_number.to_string(),
|
||||
chain_id: signer_data.chain_id.to_string(),
|
||||
fee: Fee {
|
||||
amount: fee.amount.iter().cloned().map(Into::into).collect(),
|
||||
gas: fee.gas_limit.to_string(),
|
||||
},
|
||||
memo: memo.clone(),
|
||||
msgs: vec![send_msg],
|
||||
sequence: signer_data.sequence.to_string(),
|
||||
};
|
||||
let tx_bytes = self
|
||||
.sign(
|
||||
serde_json::to_string(&tx).unwrap(),
|
||||
vec![cosmrs_msg],
|
||||
fee,
|
||||
memo,
|
||||
signer_data,
|
||||
)?
|
||||
.to_bytes()
|
||||
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
|
||||
|
||||
self.broadcast_tx(tx_bytes.into()).await
|
||||
}
|
||||
|
||||
async fn wallet_execute(
|
||||
&self,
|
||||
sender_address: &AccountId,
|
||||
contract_address: &AccountId,
|
||||
msg: &ExecuteMsg,
|
||||
fee: crate::nymd::fee::Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
funds: Vec<crate::nymd::Coin>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
let cosmrs_msg = cosmrs::cosmwasm::MsgExecuteContract {
|
||||
sender: sender_address.clone(),
|
||||
contract: contract_address.clone(),
|
||||
msg: serde_json::to_vec(msg)?,
|
||||
funds: funds.iter().cloned().map(Into::into).collect(),
|
||||
}
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))?;
|
||||
let memo = memo.into();
|
||||
let fee = self
|
||||
.determine_transaction_fee(sender_address, &vec![cosmrs_msg.clone()], fee, &memo)
|
||||
.await?;
|
||||
|
||||
let response = self.signer().get_addr_secp265k1(false)?;
|
||||
let sequence_response = self
|
||||
.get_sequence(&AccountId::from_str(&response.address).unwrap())
|
||||
.await?;
|
||||
let chain_id = self.get_chain_id().await?;
|
||||
|
||||
let signer_data = SignerData {
|
||||
account_number: sequence_response.account_number,
|
||||
sequence: sequence_response.sequence,
|
||||
chain_id,
|
||||
};
|
||||
|
||||
let execute_contract_msg = MsgExecuteContract {
|
||||
type_url: String::from("wasm/MsgExecuteContract"),
|
||||
value: ExecuteContractValue {
|
||||
contract: contract_address.to_string(),
|
||||
funds: funds.iter().cloned().map(Into::into).collect(),
|
||||
msg: msg.clone(),
|
||||
sender: sender_address.to_string(),
|
||||
},
|
||||
};
|
||||
let tx = ExecuteContractTransaction {
|
||||
account_number: signer_data.account_number.to_string(),
|
||||
chain_id: signer_data.chain_id.to_string(),
|
||||
fee: Fee {
|
||||
amount: fee.amount.iter().cloned().map(Into::into).collect(),
|
||||
gas: fee.gas_limit.to_string(),
|
||||
},
|
||||
memo: memo.clone(),
|
||||
msgs: vec![execute_contract_msg],
|
||||
sequence: signer_data.sequence.to_string(),
|
||||
};
|
||||
let tx_bytes = self
|
||||
.sign(
|
||||
serde_json::to_string(&tx).unwrap(),
|
||||
vec![cosmrs_msg],
|
||||
fee,
|
||||
memo,
|
||||
signer_data,
|
||||
)?
|
||||
.to_bytes()
|
||||
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
|
||||
|
||||
let tx_res = self.broadcast_tx(tx_bytes.into()).await?.check_response()?;
|
||||
|
||||
let gas_info = GasInfo::new(tx_res.tx_result.gas_wanted, tx_res.tx_result.gas_used);
|
||||
|
||||
Ok(ExecuteResult {
|
||||
logs: parse_raw_logs(tx_res.tx_result.log)?,
|
||||
data: tx_res.tx_result.data,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -133,6 +133,9 @@ pub enum NymdError {
|
||||
|
||||
#[error("Account had an unexpected bech32 prefix. Expected: {expected}, got: {got}")]
|
||||
UnexpectedBech32Prefix { got: String, expected: String },
|
||||
|
||||
#[error("Ledger error: {0}")]
|
||||
LedgerError(#[from] ledger::error::LedgerError),
|
||||
}
|
||||
|
||||
impl NymdError {
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::nymd::cosmwasm_client::types::{
|
||||
Account, ChangeAdminResult, ContractCodeId, ExecuteResult, InstantiateOptions,
|
||||
InstantiateResult, MigrateResult, SequenceResponse, SimulateResponse, UploadResult,
|
||||
};
|
||||
use crate::nymd::cosmwasm_client::wallet_client;
|
||||
use crate::nymd::error::NymdError;
|
||||
use crate::nymd::fee::DEFAULT_SIMULATED_GAS_MULTIPLIER;
|
||||
use crate::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
@@ -28,12 +29,14 @@ use mixnet_contract_common::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryInto;
|
||||
use std::str::FromStr;
|
||||
use std::time::SystemTime;
|
||||
use vesting_contract_common::ExecuteMsg as VestingExecuteMsg;
|
||||
use vesting_contract_common::QueryMsg as VestingQueryMsg;
|
||||
|
||||
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
|
||||
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
|
||||
pub use crate::nymd::cosmwasm_client::wallet_client::MinimalSigningCosmWasmClient;
|
||||
pub use crate::nymd::fee::Fee;
|
||||
pub use coin::Coin;
|
||||
pub use cosmrs::bank::MsgSend;
|
||||
@@ -52,9 +55,11 @@ pub use cosmrs::Coin as CosmosCoin;
|
||||
pub use cosmrs::{bip32, AccountId, Decimal, Denom};
|
||||
pub use cosmwasm_std::Coin as CosmWasmCoin;
|
||||
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
|
||||
use network_defaults::{ChainDetails, NymNetworkDetails};
|
||||
use ledger::CosmosLedger;
|
||||
use network_defaults::{ChainDetails, NymNetworkDetails, COSMOS_DERIVATION_PATH};
|
||||
pub use signing_client::Client as SigningNymdClient;
|
||||
pub use traits::{VestingQueryClient, VestingSigningClient};
|
||||
pub use wallet_client::Client as WalletNymdClient;
|
||||
|
||||
pub mod coin;
|
||||
pub mod cosmwasm_client;
|
||||
@@ -209,6 +214,34 @@ impl NymdClient<SigningNymdClient> {
|
||||
}
|
||||
}
|
||||
|
||||
impl NymdClient<WalletNymdClient> {
|
||||
pub fn connect_with_ledger<U: Clone>(
|
||||
config: Config,
|
||||
endpoint: U,
|
||||
signer: CosmosLedger,
|
||||
gas_price: Option<GasPrice>,
|
||||
) -> Result<NymdClient<WalletNymdClient>, NymdError>
|
||||
where
|
||||
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
|
||||
{
|
||||
let denom = &config.chain_details.mix_denom.base;
|
||||
let client_address =
|
||||
AccountId::from_str(&signer.get_addr_secp265k1(false).unwrap().address).unwrap();
|
||||
let gas_price = gas_price.unwrap_or(GasPrice::new_with_default_price(denom)?);
|
||||
|
||||
Ok(NymdClient {
|
||||
client: WalletNymdClient::connect_with_ledger(endpoint, signer, gas_price)?,
|
||||
config,
|
||||
client_address: Some(vec![client_address]),
|
||||
simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ledger_signer(&self) -> CosmosLedger {
|
||||
self.client.signer()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> NymdClient<C> {
|
||||
pub fn current_config(&self) -> &Config {
|
||||
&self.config
|
||||
@@ -332,7 +365,7 @@ impl<C> NymdClient<C> {
|
||||
|
||||
pub fn address(&self) -> &AccountId
|
||||
where
|
||||
C: SigningCosmWasmClient,
|
||||
C: MinimalSigningCosmWasmClient,
|
||||
{
|
||||
// if this is a signing client (as required by the trait bound), it must have the address set
|
||||
&self.client_address.as_ref().unwrap()[0]
|
||||
@@ -342,14 +375,14 @@ impl<C> NymdClient<C> {
|
||||
where
|
||||
C: SigningCosmWasmClient,
|
||||
{
|
||||
self.client.signer()
|
||||
SigningCosmWasmClient::signer(&self.client)
|
||||
}
|
||||
|
||||
pub fn gas_price(&self) -> &GasPrice
|
||||
where
|
||||
C: SigningCosmWasmClient,
|
||||
C: MinimalSigningCosmWasmClient,
|
||||
{
|
||||
self.client.gas_price()
|
||||
MinimalSigningCosmWasmClient::gas_price(&self.client)
|
||||
}
|
||||
|
||||
pub fn gas_adjustment(&self) -> GasAdjustment {
|
||||
@@ -896,6 +929,29 @@ impl<C> NymdClient<C> {
|
||||
) -> Result<TxResponse, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
|
||||
SigningCosmWasmClient::send_tokens(
|
||||
&self.client,
|
||||
self.address(),
|
||||
recipient,
|
||||
amount,
|
||||
fee,
|
||||
memo,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Send funds from one address to another
|
||||
pub async fn wallet_send(
|
||||
&self,
|
||||
recipient: &AccountId,
|
||||
amount: Vec<Coin>,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<TxResponse, NymdError>
|
||||
where
|
||||
C: MinimalSigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
|
||||
self.client
|
||||
@@ -980,6 +1036,23 @@ impl<C> NymdClient<C> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn wallet_execute(
|
||||
&self,
|
||||
contract_address: &AccountId,
|
||||
msg: &ExecuteMsg,
|
||||
fee: Option<Fee>,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
funds: Vec<Coin>,
|
||||
) -> Result<ExecuteResult, NymdError>
|
||||
where
|
||||
C: MinimalSigningCosmWasmClient + Sync,
|
||||
{
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
|
||||
self.client
|
||||
.wallet_execute(self.address(), contract_address, msg, fee, memo, funds)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn execute_multiple<I, M>(
|
||||
&self,
|
||||
contract_address: &AccountId,
|
||||
|
||||
@@ -17,6 +17,11 @@ pub fn execute(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
} else {
|
||||
panic!("Only `mixnet` and `vesting` targets are supported!")
|
||||
};
|
||||
let exe = if target == "mixnet" {
|
||||
quote!(wallet_execute)
|
||||
} else {
|
||||
quote!(execute)
|
||||
};
|
||||
let cl = proc_macro::TokenStream::from(cl);
|
||||
let cl = parse_macro_input!(cl as ExprMethodCall);
|
||||
|
||||
@@ -85,7 +90,7 @@ pub fn execute(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let (req, fee) = self.#name(#(#execute_args),*);
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
|
||||
self.client
|
||||
.execute(
|
||||
.#exe(
|
||||
self.address(),
|
||||
#cl,
|
||||
&req,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "ledger"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bip32 = "0.3.0"
|
||||
k256 = "0.10.4"
|
||||
ledger-transport = "0.10.0"
|
||||
ledger-transport-hid = "0.10.0"
|
||||
thiserror = "1"
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::LedgerError;
|
||||
use crate::helpers::answer_bytes;
|
||||
use bip32::{PublicKey, PublicKeyBytes};
|
||||
use ledger_transport::APDUAnswer;
|
||||
|
||||
/// SECP265K1 address of the device.
|
||||
pub struct AddrSecp265k1Response {
|
||||
/// SECP265K1 public key.
|
||||
pub public_key: k256::PublicKey,
|
||||
/// String representation of the Cosmos address.
|
||||
pub address: String,
|
||||
}
|
||||
|
||||
impl TryFrom<APDUAnswer<Vec<u8>>> for AddrSecp265k1Response {
|
||||
type Error = LedgerError;
|
||||
|
||||
fn try_from(answer: APDUAnswer<Vec<u8>>) -> Result<Self, Self::Error> {
|
||||
let bytes = answer_bytes(&answer)?;
|
||||
if bytes.len() < 33 {
|
||||
return Err(Self::Error::InvalidAnswerLength {
|
||||
expected: 33,
|
||||
received: bytes.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let (pub_key, addr) = bytes.split_at(33);
|
||||
let public_key = k256::PublicKey::from_bytes(
|
||||
PublicKeyBytes::try_from(pub_key).expect("Public key should be 33 bytes"),
|
||||
)?;
|
||||
let address = String::from_utf8(addr.to_vec()).unwrap();
|
||||
|
||||
Ok(AddrSecp265k1Response {
|
||||
public_key,
|
||||
address,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
pub(crate) type Result<T> = std::result::Result<T, LedgerError>;
|
||||
|
||||
/// Ledger specific errors.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum LedgerError {
|
||||
#[error("HID API - {0}")]
|
||||
HidAPI(#[from] ledger_transport_hid::hidapi::HidError),
|
||||
|
||||
#[error("HID transport - {0}")]
|
||||
HidTransport(#[from] ledger_transport_hid::LedgerHIDError),
|
||||
|
||||
#[error("Unknown error code - {err_code}")]
|
||||
UnknownErrorCode { err_code: u16 },
|
||||
|
||||
#[error("APDU error - {reason}")]
|
||||
APDU { reason: String },
|
||||
|
||||
#[error("Not enough bytes in answer. Expected at least {expected}, received {received}")]
|
||||
InvalidAnswerLength { expected: usize, received: usize },
|
||||
|
||||
#[error("Not enough components in derivation path. Expected {expected}, received {received}")]
|
||||
InvalidDerivationPath { expected: usize, received: usize },
|
||||
|
||||
#[error("Bip32 - {0}")]
|
||||
Bip32(#[from] bip32::Error),
|
||||
|
||||
#[error("Signature error - {0}")]
|
||||
Signature(#[from] k256::ecdsa::Error),
|
||||
|
||||
#[error("No message found for signing transaction")]
|
||||
NoMessageFound,
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::{LedgerError, Result};
|
||||
use bip32::DerivationPath;
|
||||
use ledger_transport::{APDUAnswer, APDUErrorCode};
|
||||
|
||||
pub(crate) fn answer_bytes(answer: &APDUAnswer<Vec<u8>>) -> Result<&[u8]> {
|
||||
let error_code = answer
|
||||
.error_code()
|
||||
.map_err(|err_code| LedgerError::UnknownErrorCode { err_code })?;
|
||||
match error_code {
|
||||
APDUErrorCode::NoError => Ok(answer.data()),
|
||||
e => Err(LedgerError::APDU {
|
||||
reason: e.description(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn path_bytes(path: DerivationPath) -> Result<[[u8; 4]; 5]> {
|
||||
let received = path.len();
|
||||
let components: Vec<[u8; 4]> = path.into_iter().map(|c| c.0.to_le_bytes()).collect();
|
||||
if components.len() != 5 {
|
||||
Err(LedgerError::InvalidDerivationPath {
|
||||
expected: 5,
|
||||
received,
|
||||
})
|
||||
} else {
|
||||
Ok([
|
||||
components[0],
|
||||
components[1],
|
||||
components[2],
|
||||
components[3],
|
||||
components[4],
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod addr_secp265k1;
|
||||
pub mod error;
|
||||
pub(crate) mod helpers;
|
||||
mod sign_secp265k1;
|
||||
pub mod version;
|
||||
|
||||
use crate::addr_secp265k1::AddrSecp265k1Response;
|
||||
use crate::error::LedgerError;
|
||||
use crate::helpers::path_bytes;
|
||||
use crate::sign_secp265k1::SignSecp265k1Response;
|
||||
use crate::version::VersionResponse;
|
||||
use bip32::DerivationPath;
|
||||
use error::Result;
|
||||
use ledger_transport::APDUCommand;
|
||||
use ledger_transport_hid::hidapi::HidApi;
|
||||
use ledger_transport_hid::TransportNativeHID;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::Formatter;
|
||||
use std::sync::Arc;
|
||||
|
||||
const CLA: u8 = 0x55;
|
||||
const INS_GET_VERSION: u8 = 0x00;
|
||||
const INS_SIGN_SECP256K1: u8 = 0x02;
|
||||
const INS_GET_ADDR_SECP256K1: u8 = 0x04;
|
||||
|
||||
const PAYLOAD_TYPE_INIT: u8 = 0x00;
|
||||
const PAYLOAD_TYPE_ADD: u8 = 0x01;
|
||||
const PAYLOAD_TYPE_LAST: u8 = 0x02;
|
||||
const CHUNK_SIZE: usize = 250;
|
||||
|
||||
/// Manage hardware Ledger device with Cosmos specific operations, as described in the
|
||||
/// specification: https://github.com/cosmos/ledger-cosmos/blob/main/docs/APDUSPEC.md
|
||||
#[derive(Clone)]
|
||||
pub struct CosmosLedger {
|
||||
path: DerivationPath,
|
||||
prefix: String,
|
||||
transport: Arc<TransportNativeHID>,
|
||||
}
|
||||
|
||||
impl Debug for CosmosLedger {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
write!(f, "()")
|
||||
}
|
||||
}
|
||||
|
||||
impl CosmosLedger {
|
||||
/// Create the connection to the first Ledger device that we can find.
|
||||
pub fn new(path: DerivationPath, prefix: String) -> Result<Self> {
|
||||
let api = HidApi::new()?;
|
||||
let transport = Arc::new(TransportNativeHID::new(&api)?);
|
||||
|
||||
Ok(CosmosLedger {
|
||||
path,
|
||||
prefix,
|
||||
transport,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the version of the device.
|
||||
pub fn get_version(&self) -> Result<VersionResponse> {
|
||||
let command = APDUCommand {
|
||||
cla: CLA,
|
||||
ins: INS_GET_VERSION,
|
||||
p1: 0,
|
||||
p2: 0,
|
||||
data: vec![],
|
||||
};
|
||||
let response = self.transport.exchange(&command)?;
|
||||
VersionResponse::try_from(response)
|
||||
}
|
||||
|
||||
/// Get the SECP265K1 address of the device.
|
||||
pub fn get_addr_secp265k1(&self, display: bool) -> Result<AddrSecp265k1Response> {
|
||||
let display = if display { 1 } else { 0 };
|
||||
let components = path_bytes(self.path.clone())?;
|
||||
let data: Vec<u8> = vec![
|
||||
[self.prefix.len() as u8].as_slice(),
|
||||
self.prefix.as_bytes(),
|
||||
components[0].as_slice(),
|
||||
components[1].as_slice(),
|
||||
components[2].as_slice(),
|
||||
components[3].as_slice(),
|
||||
components[4].as_slice(),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
let command = APDUCommand {
|
||||
cla: CLA,
|
||||
ins: INS_GET_ADDR_SECP256K1,
|
||||
p1: display,
|
||||
p2: 0,
|
||||
data,
|
||||
};
|
||||
let response = self.transport.exchange(&command)?;
|
||||
AddrSecp265k1Response::try_from(response)
|
||||
}
|
||||
|
||||
pub fn sign_secp265k1(&self, message: String) -> Result<SignSecp265k1Response> {
|
||||
let serialized_path: Vec<u8> = path_bytes(self.path.clone())?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect();
|
||||
let mut chunks = vec![serialized_path];
|
||||
if message.is_empty() {
|
||||
return Err(LedgerError::NoMessageFound);
|
||||
}
|
||||
for chunk in message.into_bytes().chunks(CHUNK_SIZE) {
|
||||
chunks.push(chunk.to_vec());
|
||||
}
|
||||
let length = chunks.len();
|
||||
for (idx, chunk) in chunks.into_iter().enumerate() {
|
||||
let payload_desc = if idx == 0 {
|
||||
PAYLOAD_TYPE_INIT
|
||||
} else if idx + 1 == length {
|
||||
PAYLOAD_TYPE_LAST
|
||||
} else {
|
||||
PAYLOAD_TYPE_ADD
|
||||
};
|
||||
let command = APDUCommand {
|
||||
cla: CLA,
|
||||
ins: INS_SIGN_SECP256K1,
|
||||
p1: payload_desc,
|
||||
p2: 0,
|
||||
data: chunk,
|
||||
};
|
||||
let sign_response = self.transport.exchange(&command)?;
|
||||
if payload_desc == PAYLOAD_TYPE_LAST {
|
||||
return SignSecp265k1Response::try_from(sign_response);
|
||||
}
|
||||
}
|
||||
// It should never reach this, as the message is not empty
|
||||
Err(LedgerError::NoMessageFound)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::LedgerError;
|
||||
use crate::helpers::answer_bytes;
|
||||
use k256::ecdsa::Signature;
|
||||
use ledger_transport::APDUAnswer;
|
||||
|
||||
/// Version and status data of the device.
|
||||
pub struct SignSecp265k1Response {
|
||||
/// DER encoded signature data
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
impl TryFrom<APDUAnswer<Vec<u8>>> for SignSecp265k1Response {
|
||||
type Error = LedgerError;
|
||||
|
||||
fn try_from(answer: APDUAnswer<Vec<u8>>) -> Result<Self, Self::Error> {
|
||||
let bytes = answer_bytes(&answer)?;
|
||||
|
||||
Ok(SignSecp265k1Response {
|
||||
signature: Signature::from_der(bytes)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::LedgerError;
|
||||
use crate::helpers::answer_bytes;
|
||||
use ledger_transport::APDUAnswer;
|
||||
|
||||
/// Version and status data of the device.
|
||||
pub struct VersionResponse {
|
||||
/// Activation status of test mode.
|
||||
pub test_mode: bool,
|
||||
/// Major part of Cosmos application version.
|
||||
pub major: u8,
|
||||
/// Minor part of Cosmos application version.
|
||||
pub minor: u8,
|
||||
/// Patch part of Cosmos application version.
|
||||
pub patch: u8,
|
||||
/// PIN locked status.
|
||||
pub device_locked: bool,
|
||||
}
|
||||
|
||||
impl TryFrom<APDUAnswer<Vec<u8>>> for VersionResponse {
|
||||
type Error = LedgerError;
|
||||
|
||||
fn try_from(answer: APDUAnswer<Vec<u8>>) -> Result<Self, Self::Error> {
|
||||
let bytes = answer_bytes(&answer)?;
|
||||
if bytes.len() != 5 {
|
||||
return Err(Self::Error::InvalidAnswerLength {
|
||||
expected: 5,
|
||||
received: bytes.len(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(VersionResponse {
|
||||
test_mode: bytes[0] != 0,
|
||||
major: bytes[1],
|
||||
minor: bytes[2],
|
||||
patch: bytes[3],
|
||||
device_locked: bytes[4] != 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ ts-rs = "6.1.2"
|
||||
cosmwasm-std = "1.0.0-beta8"
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
|
||||
ledger = { path = "../../common/ledger" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = [
|
||||
"nymd-client",
|
||||
] }
|
||||
|
||||
@@ -70,6 +70,11 @@ pub enum TypesError {
|
||||
LossyCoinConversion,
|
||||
#[error("The provided coin has an unknown denomination - {0}")]
|
||||
UnknownCoinDenom(String),
|
||||
#[error("{source}")]
|
||||
LedgerError {
|
||||
#[from]
|
||||
source: ledger::error::LedgerError,
|
||||
},
|
||||
}
|
||||
|
||||
impl Serialize for TypesError {
|
||||
@@ -88,6 +93,7 @@ impl From<ValidatorClientError> for TypesError {
|
||||
ValidatorClientError::MalformedUrlProvided(e) => e.into(),
|
||||
ValidatorClientError::NymdError(e) => e.into(),
|
||||
ValidatorClientError::NoAPIUrlAvailable => TypesError::NoValidatorApiUrlConfigured,
|
||||
ValidatorClientError::LedgerError(e) => e.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+102
@@ -116,6 +116,12 @@ dependencies = [
|
||||
"password-hash 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.52"
|
||||
@@ -1131,6 +1137,12 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.15.0"
|
||||
@@ -1996,6 +2008,17 @@ version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0"
|
||||
|
||||
[[package]]
|
||||
name = "hidapi"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d26e1151deaab68f34fbfd16d491a2a0170cf98d69d3efa23873b567a4199e1"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.11.0"
|
||||
@@ -2387,6 +2410,54 @@ version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "ledger"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bip32",
|
||||
"k256",
|
||||
"ledger-transport",
|
||||
"ledger-transport-hid",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ledger-apdu"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe435806c197dfeaa5efcded5e623c4b8230fd28fdf1e91e7a86e40ef2acbf90"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"no-std-compat",
|
||||
"snafu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ledger-transport"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1117f2143d92c157197785bf57711d7b02f2cfa101e162f8ca7900fb7f976321"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"ledger-apdu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ledger-transport-hid"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45ba81a1f5f24396b37211478aff7fbcd605dd4544df8dbed07b9da3c2057aee"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"cfg-if",
|
||||
"hex",
|
||||
"hidapi",
|
||||
"ledger-transport",
|
||||
"libc",
|
||||
"log",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.119"
|
||||
@@ -2654,6 +2725,12 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
||||
|
||||
[[package]]
|
||||
name = "no-std-compat"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
|
||||
|
||||
[[package]]
|
||||
name = "nodrop"
|
||||
version = "0.1.14"
|
||||
@@ -2771,6 +2848,7 @@ dependencies = [
|
||||
"cosmwasm-std",
|
||||
"eyre",
|
||||
"itertools",
|
||||
"ledger",
|
||||
"log",
|
||||
"mixnet-contract-common",
|
||||
"reqwest",
|
||||
@@ -2841,6 +2919,7 @@ dependencies = [
|
||||
"futures",
|
||||
"itertools",
|
||||
"k256",
|
||||
"ledger",
|
||||
"log",
|
||||
"mixnet-contract-common",
|
||||
"nym-types",
|
||||
@@ -4342,6 +4421,28 @@ version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
|
||||
|
||||
[[package]]
|
||||
name = "snafu"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5177903bf45656592d9eb5c0e22f408fc023aae51dbe2088889b71633ba451f2"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
"snafu-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu-derive"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "410b26ed97440d90ced3e2488c868d56a86e2064f5d7d6f417909b286afe25e5"
|
||||
dependencies = [
|
||||
"heck 0.4.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.4"
|
||||
@@ -5280,6 +5381,7 @@ dependencies = [
|
||||
"flate2",
|
||||
"futures",
|
||||
"itertools",
|
||||
"ledger",
|
||||
"log",
|
||||
"mixnet-contract-common",
|
||||
"multisig-contract-common",
|
||||
|
||||
Vendored
@@ -61,6 +61,7 @@ mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-
|
||||
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" }
|
||||
config = { path = "../../common/config" }
|
||||
coconut-interface = { path = "../../common/coconut-interface" }
|
||||
ledger = { path = "../../common/ledger" }
|
||||
# Used for Type conversion, can be extracted but its a lot of work
|
||||
vesting-contract = { path = "../../contracts/vesting" }
|
||||
nym-types = { path = "../../common/types" }
|
||||
|
||||
@@ -120,6 +120,8 @@ pub enum BackendError {
|
||||
SignatureError(String),
|
||||
#[error("Unable to open a new window")]
|
||||
NewWindowError,
|
||||
#[error("{0}")]
|
||||
LedgerError(#[from] ledger::error::LedgerError),
|
||||
}
|
||||
|
||||
impl Serialize for BackendError {
|
||||
@@ -140,6 +142,7 @@ impl From<ValidatorClientError> for BackendError {
|
||||
ValidatorClientError::NoAPIUrlAvailable => {
|
||||
TypesError::NoValidatorApiUrlConfigured.into()
|
||||
}
|
||||
ValidatorClientError::LedgerError(e) => e.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use std::str::FromStr;
|
||||
use strum::IntoEnumIterator;
|
||||
use url::Url;
|
||||
use validator_client::nymd::wallet::{AccountData, DirectSecp256k1HdWallet};
|
||||
use validator_client::{nymd::SigningNymdClient, Client};
|
||||
use validator_client::{nymd::WalletNymdClient, Client};
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn connect_with_mnemonic(
|
||||
@@ -205,9 +205,9 @@ fn create_clients(
|
||||
default_api_urls: &HashMap<WalletNetwork, Url>,
|
||||
config: &Config,
|
||||
mnemonic: &Mnemonic,
|
||||
) -> Result<Vec<(WalletNetwork, Client<SigningNymdClient>)>, BackendError> {
|
||||
) -> Result<Vec<(WalletNetwork, Client<WalletNymdClient>)>, BackendError> {
|
||||
let mut clients = Vec::new();
|
||||
for network in WalletNetwork::iter() {
|
||||
for network in [WalletNetwork::MAINNET] {
|
||||
let nymd_url = if let Some(url) = config.get_selected_validator_nymd_url(network) {
|
||||
log::debug!("Using selected nymd_url for {network}: {url}");
|
||||
url.clone()
|
||||
@@ -252,7 +252,7 @@ fn create_clients(
|
||||
let config = validator_client::Config::try_from_nym_network_details(&network_details)?
|
||||
.with_urls(nymd_url, api_url);
|
||||
|
||||
let mut client = validator_client::Client::new_signing(config, mnemonic.clone())?;
|
||||
let mut client = validator_client::Client::new_ledger(config)?;
|
||||
client.set_nymd_simulated_gas_multiplier(CUSTOM_SIMULATED_GAS_MULTIPLIER);
|
||||
clients.push((network, client));
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ pub async fn send(
|
||||
let raw_res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.send(&to_address, vec![amount_base], memo, fee)
|
||||
.wallet_send(&to_address, vec![amount_base], memo, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", raw_res.hash.to_string());
|
||||
let res = SendTxResult::new(
|
||||
|
||||
@@ -15,7 +15,7 @@ use strum::IntoEnumIterator;
|
||||
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
use url::Url;
|
||||
use validator_client::nymd::cosmwasm_client::types::SimulateResponse;
|
||||
use validator_client::nymd::{AccountId as CosmosAccountId, Coin, Fee, SigningNymdClient};
|
||||
use validator_client::nymd::{AccountId as CosmosAccountId, Coin, Fee, WalletNymdClient};
|
||||
use validator_client::Client;
|
||||
|
||||
// Some hardcoded metadata overrides
|
||||
@@ -63,7 +63,7 @@ impl WalletState {
|
||||
#[derive(Default)]
|
||||
pub struct WalletStateInner {
|
||||
config: config::Config,
|
||||
signing_clients: HashMap<Network, Client<SigningNymdClient>>,
|
||||
signing_clients: HashMap<Network, Client<WalletNymdClient>>,
|
||||
current_network: Network,
|
||||
|
||||
// All the accounts the we get from decrypting the wallet. We hold on to these for being able to
|
||||
@@ -149,7 +149,7 @@ impl WalletStateInner {
|
||||
Ok(FeeDetails::new(amount, res.to_fee()))
|
||||
}
|
||||
|
||||
pub fn client(&self, network: Network) -> Result<&Client<SigningNymdClient>, BackendError> {
|
||||
pub fn client(&self, network: Network) -> Result<&Client<WalletNymdClient>, BackendError> {
|
||||
self.signing_clients
|
||||
.get(&network)
|
||||
.ok_or(BackendError::ClientNotInitialized)
|
||||
@@ -158,20 +158,20 @@ impl WalletStateInner {
|
||||
pub fn client_mut(
|
||||
&mut self,
|
||||
network: Network,
|
||||
) -> Result<&mut Client<SigningNymdClient>, BackendError> {
|
||||
) -> Result<&mut Client<WalletNymdClient>, BackendError> {
|
||||
self.signing_clients
|
||||
.get_mut(&network)
|
||||
.ok_or(BackendError::ClientNotInitialized)
|
||||
}
|
||||
|
||||
pub fn current_client(&self) -> Result<&Client<SigningNymdClient>, BackendError> {
|
||||
pub fn current_client(&self) -> Result<&Client<WalletNymdClient>, BackendError> {
|
||||
self.signing_clients
|
||||
.get(&self.current_network)
|
||||
.ok_or(BackendError::ClientNotInitialized)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn current_client_mut(&mut self) -> Result<&mut Client<SigningNymdClient>, BackendError> {
|
||||
pub fn current_client_mut(&mut self) -> Result<&mut Client<WalletNymdClient>, BackendError> {
|
||||
self.signing_clients
|
||||
.get_mut(&self.current_network)
|
||||
.ok_or(BackendError::ClientNotInitialized)
|
||||
@@ -191,7 +191,7 @@ impl WalletStateInner {
|
||||
Ok(self.config.save_to_files()?)
|
||||
}
|
||||
|
||||
pub fn add_client(&mut self, network: Network, client: Client<SigningNymdClient>) {
|
||||
pub fn add_client(&mut self, network: Network, client: Client<WalletNymdClient>) {
|
||||
self.signing_clients.insert(network, client);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user