diff --git a/mm2src/adex_cli/src/scenarios/init_mm2_cfg.rs b/mm2src/adex_cli/src/scenarios/init_mm2_cfg.rs index b21c3f292b..60b3fbe445 100644 --- a/mm2src/adex_cli/src/scenarios/init_mm2_cfg.rs +++ b/mm2src/adex_cli/src/scenarios/init_mm2_cfg.rs @@ -51,7 +51,7 @@ struct Mm2Cfg { #[serde(skip_serializing_if = "Vec::::is_empty")] seednodes: Vec, #[serde(skip_serializing_if = "Option::is_none")] - hd_account_id: Option, + enable_hd: Option, } impl Mm2Cfg { @@ -68,7 +68,7 @@ impl Mm2Cfg { rpc_local_only: None, i_am_seed: None, seednodes: Vec::::new(), - hd_account_id: None, + enable_hd: None, } } @@ -84,7 +84,7 @@ impl Mm2Cfg { self.inquire_rpc_local_only()?; self.inquire_i_am_a_seed()?; self.inquire_seednodes()?; - self.inquire_hd_account_id()?; + self.inquire_enable_hd()?; Ok(()) } @@ -311,13 +311,16 @@ impl Mm2Cfg { } #[inline] - fn inquire_hd_account_id(&mut self) -> Result<()> { - self.hd_account_id = CustomType::>::new("What is hd_account_id:") - .with_help_message(r#"Optional. If this value is set, the AtomicDEX-API will work in only the HD derivation mode, coins will need to have a coin derivation path entry in the coins file for activation. The hd_account_id value effectively takes its place in the full derivation as follows: m/44'/COIN_ID'/'/CHAIN/ADDRESS_ID"#) - .with_placeholder(DEFAULT_OPTION_PLACEHOLDER) + fn inquire_enable_hd(&mut self) -> Result<()> { + self.enable_hd = CustomType::>::new("What is enable_hd:") + .with_parser(OPTION_BOOL_PARSER) + .with_formatter(DEFAULT_OPTION_BOOL_FORMATTER) + .with_default_value_formatter(DEFAULT_DEFAULT_OPTION_BOOL_FORMATTER) + .with_default(InquireOption::None) + .with_help_message(r#"Optional. If this value is set, the Komodo DeFi API will work in HD wallet mode only, coins will need to have a coin derivation path entry in the coins file for activation. path_to_address `/account'/change/address_index` will have to be set in coins activation to change the default HD wallet address that is used in swaps for a coin in the full derivation path as follows: m/purpose'/coin_type/account'/change/address_index"#) .prompt() .map_err(|error| - error_anyhow!("Failed to get hd_account_id: {}", error) + error_anyhow!("Failed to get enable_hd: {}", error) )? .into(); Ok(()) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index b9cf3e3fad..cba9ece714 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -34,7 +34,7 @@ use common::{get_utc_timestamp, now_sec, small_rng, DEX_FEE_ADDR_RAW_PUBKEY}; #[cfg(target_arch = "wasm32")] use common::{now_ms, wait_until_ms}; use crypto::privkey::key_pair_from_secret; -use crypto::{CryptoCtx, CryptoCtxError, GlobalHDAccountArc, KeyPairPolicy}; +use crypto::{CryptoCtx, CryptoCtxError, GlobalHDAccountArc, KeyPairPolicy, StandardHDCoinAddress}; use derive_more::Display; use enum_from::EnumFromStringify; use ethabi::{Contract, Function, Token}; @@ -110,7 +110,7 @@ use crate::nft::{find_wallet_nft_amount, WithdrawNftResult}; use v2_activation::{build_address_and_priv_key_policy, EthActivationV2Error}; mod nonce; -use crate::TransactionResult; +use crate::{PrivKeyPolicy, TransactionResult, WithdrawFrom}; use nonce::ParityNonce; /// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol @@ -171,6 +171,7 @@ lazy_static! { pub type Web3RpcFut = Box> + Send>; pub type Web3RpcResult = Result>; pub type GasStationResult = Result>; +type EthPrivKeyPolicy = PrivKeyPolicy; type GasDetails = (U256, U256); #[derive(Debug, Display)] @@ -410,35 +411,6 @@ impl TryFrom for EthPrivKeyBuildPolicy { } } -/// An alternative to `crate::PrivKeyPolicy`, typical only for ETH coin. -#[derive(Clone)] -pub enum EthPrivKeyPolicy { - KeyPair(KeyPair), - #[cfg(target_arch = "wasm32")] - Metamask(EthMetamaskPolicy), -} - -#[cfg(target_arch = "wasm32")] -#[derive(Clone)] -pub struct EthMetamaskPolicy { - pub(crate) public_key: H264, - pub(crate) public_key_uncompressed: H520, -} - -impl From for EthPrivKeyPolicy { - fn from(key_pair: KeyPair) -> Self { EthPrivKeyPolicy::KeyPair(key_pair) } -} - -impl EthPrivKeyPolicy { - pub fn key_pair_or_err(&self) -> MmResult<&KeyPair, PrivKeyPolicyNotAllowed> { - match self { - EthPrivKeyPolicy::KeyPair(key_pair) => Ok(key_pair), - #[cfg(target_arch = "wasm32")] - EthPrivKeyPolicy::Metamask(_) => MmError::err(PrivKeyPolicyNotAllowed::HardwareWalletNotSupported), - } - } -} - /// pImpl idiom. pub struct EthCoinImpl { ticker: String, @@ -736,7 +708,28 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { let to_addr = coin .address_from_str(&req.to) .map_to_mm(WithdrawError::InvalidAddress)?; - let my_balance = coin.my_balance().compat().await?; + let (my_balance, my_address, key_pair) = match req.from { + Some(WithdrawFrom::HDWalletAddress(ref path_to_address)) => { + let raw_priv_key = coin + .priv_key_policy + .hd_wallet_derived_priv_key_or_err(path_to_address)?; + let key_pair = KeyPair::from_secret_slice(raw_priv_key.as_slice()) + .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; + let address = key_pair.address(); + let balance = coin.address_balance(address).compat().await?; + (balance, address, key_pair) + }, + Some(WithdrawFrom::AddressId(_)) | Some(WithdrawFrom::DerivationPath { .. }) => { + return MmError::err(WithdrawError::UnexpectedFromAddress( + "Withdraw from 'AddressId' or 'DerivationPath' is not supported yet for EVM!".to_string(), + )) + }, + None => ( + coin.my_balance().compat().await?, + coin.my_address, + coin.priv_key_policy.activated_key_or_err()?.clone(), + ), + }; let my_balance_dec = u256_to_big_decimal(my_balance, coin.decimals)?; let (mut wei_amount, dec_amount) = if req.max { @@ -779,9 +772,10 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { }; let (tx_hash, tx_hex) = match coin.priv_key_policy { - EthPrivKeyPolicy::KeyPair(ref key_pair) => { + EthPrivKeyPolicy::Iguana(_) | EthPrivKeyPolicy::HDWallet { .. } => { + // Todo: nonce_lock is still global for all addresses but this needs to be per address let _nonce_lock = coin.nonce_lock.lock().await; - let (nonce, _) = get_addr_nonce(coin.my_address, coin.web3_instances.clone()) + let (nonce, _) = get_addr_nonce(my_address, coin.web3_instances.clone()) .compat() .timeout_secs(30.) .await? @@ -801,6 +795,11 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { (signed.hash, BytesJson::from(bytes.to_vec())) }, + EthPrivKeyPolicy::Trezor => { + return MmError::err(WithdrawError::UnsupportedError( + "Trezor is not supported for EVM yet!".to_string(), + )) + }, #[cfg(target_arch = "wasm32")] EthPrivKeyPolicy::Metamask(_) => { if !req.broadcast { @@ -843,7 +842,7 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { let amount_decimal = u256_to_big_decimal(wei_amount, coin.decimals)?; let mut spent_by_me = amount_decimal.clone(); - let received_by_me = if to_addr == coin.my_address { + let received_by_me = if to_addr == my_address { amount_decimal.clone() } else { 0.into() @@ -852,10 +851,9 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { if coin.coin_type == EthCoinType::Eth { spent_by_me += &fee_details.total_fee; } - let my_address = coin.my_address()?; Ok(TransactionDetails { to: vec![checksum_address(&format!("{:#02x}", to_addr))], - from: vec![my_address], + from: vec![checksum_address(&format!("{:#02x}", my_address))], total_amount: amount_decimal, my_balance_change: &received_by_me - &spent_by_me, spent_by_me, @@ -952,7 +950,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit gas_price, }; - let secret = eth_coin.priv_key_policy.key_pair_or_err()?.secret(); + let secret = eth_coin.priv_key_policy.activated_key_or_err()?.secret(); let signed = tx.sign(secret, eth_coin.chain_id); let signed_bytes = rlp::encode(&signed); let fee_details = EthTxFeeDetails::new(gas, gas_price, fee_coin)?; @@ -1027,7 +1025,7 @@ pub async fn withdraw_erc721(ctx: MmArc, withdraw_type: WithdrawErc721) -> Withd gas_price, }; - let secret = eth_coin.priv_key_policy.key_pair_or_err()?.secret(); + let secret = eth_coin.priv_key_policy.activated_key_or_err()?.secret(); let signed = tx.sign(secret, eth_coin.chain_id); let signed_bytes = rlp::encode(&signed); let fee_details = EthTxFeeDetails::new(gas, gas_price, fee_coin)?; @@ -1307,9 +1305,12 @@ impl SwapOps for EthCoin { #[inline] fn derive_htlc_key_pair(&self, _swap_unique_data: &[u8]) -> keys::KeyPair { match self.priv_key_policy { - EthPrivKeyPolicy::KeyPair(ref key_pair) => { - key_pair_from_secret(key_pair.secret().as_bytes()).expect("valid key") - }, + EthPrivKeyPolicy::Iguana(ref key_pair) + | EthPrivKeyPolicy::HDWallet { + activated_key: ref key_pair, + .. + } => key_pair_from_secret(key_pair.secret().as_bytes()).expect("valid key"), + EthPrivKeyPolicy::Trezor => todo!(), #[cfg(target_arch = "wasm32")] EthPrivKeyPolicy::Metamask(_) => todo!(), } @@ -1318,10 +1319,15 @@ impl SwapOps for EthCoin { #[inline] fn derive_htlc_pubkey(&self, _swap_unique_data: &[u8]) -> Vec { match self.priv_key_policy { - EthPrivKeyPolicy::KeyPair(ref key_pair) => key_pair_from_secret(key_pair.secret().as_bytes()) + EthPrivKeyPolicy::Iguana(ref key_pair) + | EthPrivKeyPolicy::HDWallet { + activated_key: ref key_pair, + .. + } => key_pair_from_secret(key_pair.secret().as_bytes()) .expect("valid key") .public_slice() .to_vec(), + EthPrivKeyPolicy::Trezor => todo!(), #[cfg(target_arch = "wasm32")] EthPrivKeyPolicy::Metamask(ref metamask_policy) => metamask_policy.public_key.as_bytes().to_vec(), } @@ -1811,10 +1817,15 @@ impl MarketCoinOps for EthCoin { fn get_public_key(&self) -> Result> { match self.priv_key_policy { - EthPrivKeyPolicy::KeyPair(ref key_pair) => { + EthPrivKeyPolicy::Iguana(ref key_pair) + | EthPrivKeyPolicy::HDWallet { + activated_key: ref key_pair, + .. + } => { let uncompressed_without_prefix = hex::encode(key_pair.public()); Ok(format!("04{}", uncompressed_without_prefix)) }, + EthPrivKeyPolicy::Trezor => MmError::err(UnexpectedDerivationMethod::Trezor), #[cfg(target_arch = "wasm32")] EthPrivKeyPolicy::Metamask(ref metamask_policy) => { Ok(format!("{:02x}", metamask_policy.public_key_uncompressed)) @@ -1838,7 +1849,7 @@ impl MarketCoinOps for EthCoin { fn sign_message(&self, message: &str) -> SignatureResult { let message_hash = self.sign_message_hash(message).ok_or(SignatureError::PrefixNotFound)?; - let privkey = &self.priv_key_policy.key_pair_or_err()?.secret(); + let privkey = &self.priv_key_policy.activated_key_or_err()?.secret(); let signature = sign(privkey, &H256::from(message_hash))?; Ok(format!("0x{}", signature)) } @@ -2109,7 +2120,12 @@ impl MarketCoinOps for EthCoin { fn display_priv_key(&self) -> Result { match self.priv_key_policy { - EthPrivKeyPolicy::KeyPair(ref key_pair) => Ok(format!("{:#02x}", key_pair.secret())), + EthPrivKeyPolicy::Iguana(ref key_pair) + | EthPrivKeyPolicy::HDWallet { + activated_key: ref key_pair, + .. + } => Ok(format!("{:#02x}", key_pair.secret())), + EthPrivKeyPolicy::Trezor => ERR!("'display_priv_key' doesn't support Trezor yet!"), #[cfg(target_arch = "wasm32")] EthPrivKeyPolicy::Metamask(_) => ERR!("'display_priv_key' doesn't support MetaMask"), } @@ -2974,9 +2990,12 @@ impl EthCoin { let coin = self.clone(); let fut = async move { match coin.priv_key_policy { - EthPrivKeyPolicy::KeyPair(ref key_pair) => { - sign_and_send_transaction_with_keypair(ctx, &coin, key_pair, value, action, data, gas).await - }, + EthPrivKeyPolicy::Iguana(ref key_pair) + | EthPrivKeyPolicy::HDWallet { + activated_key: ref key_pair, + .. + } => sign_and_send_transaction_with_keypair(ctx, &coin, key_pair, value, action, data, gas).await, + EthPrivKeyPolicy::Trezor => Err(TransactionErr::Plain(ERRL!("Trezor is not supported for EVM yet!"))), #[cfg(target_arch = "wasm32")] EthPrivKeyPolicy::Metamask(_) => { sign_and_send_transaction_with_metamask(coin, value, action, data, gas).await @@ -3613,18 +3632,14 @@ impl EthCoin { } } - fn my_balance(&self) -> BalanceFut { + fn address_balance(&self, address: Address) -> BalanceFut { let coin = self.clone(); let fut = async move { match coin.coin_type { - EthCoinType::Eth => Ok(coin - .web3 - .eth() - .balance(coin.my_address, Some(BlockNumber::Latest)) - .await?), + EthCoinType::Eth => Ok(coin.web3.eth().balance(address, Some(BlockNumber::Latest)).await?), EthCoinType::Erc20 { ref token_addr, .. } => { let function = ERC20_CONTRACT.function("balanceOf")?; - let data = function.encode_input(&[Token::Address(coin.my_address)])?; + let data = function.encode_input(&[Token::Address(address)])?; let res = coin.call_request(*token_addr, None, Some(data.into())).await?; let decoded = function.decode_output(&res.0)?; @@ -3641,6 +3656,8 @@ impl EthCoin { Box::new(fut.boxed().compat()) } + fn my_balance(&self) -> BalanceFut { self.address_balance(self.my_address) } + pub async fn get_tokens_balance_list(&self) -> Result, MmError> { let coin = || self; let mut requests = Vec::new(); @@ -5140,7 +5157,12 @@ pub async fn eth_coin_from_conf_and_request( } let contract_supports_watchers = req["contract_supports_watchers"].as_bool().unwrap_or_default(); - let (my_address, key_pair) = try_s!(build_address_and_priv_key_policy(conf, priv_key_policy).await); + let path_to_address = try_s!(json::from_value::>( + req["path_to_address"].clone() + )) + .unwrap_or_default(); + let (my_address, key_pair) = + try_s!(build_address_and_priv_key_policy(conf, priv_key_policy, &path_to_address).await); let mut web3_instances = vec![]; let event_handlers = rpc_event_handlers_for_eth_transport(ctx, ticker.to_string()); @@ -5400,12 +5422,17 @@ impl From for GetEthAddressError { /// `get_eth_address` returns wallet address for coin with `ETH` protocol type. /// Note: result address has mixed-case checksum form. -pub async fn get_eth_address(ctx: &MmArc, ticker: &str) -> MmResult { +pub async fn get_eth_address( + ctx: &MmArc, + conf: &Json, + ticker: &str, + path_to_address: &StandardHDCoinAddress, +) -> MmResult { let priv_key_policy = PrivKeyBuildPolicy::detect_priv_key_policy(ctx)?; // Convert `PrivKeyBuildPolicy` to `EthPrivKeyBuildPolicy` if it's possible. let priv_key_policy = EthPrivKeyBuildPolicy::try_from(priv_key_policy)?; - let (my_address, ..) = build_address_and_priv_key_policy(&ctx.conf, priv_key_policy).await?; + let (my_address, ..) = build_address_and_priv_key_policy(conf, priv_key_policy, path_to_address).await?; let wallet_address = checksum_address(&format!("{:#02x}", my_address)); Ok(MyWalletAddress { diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index 856af882c9..fddf8da03f 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -1,6 +1,7 @@ use super::*; +#[cfg(target_arch = "wasm32")] use crate::EthMetamaskPolicy; use common::executor::AbortedError; -use crypto::{CryptoCtxError, StandardHDPathToCoin}; +use crypto::{CryptoCtxError, StandardHDCoinAddress}; use enum_from::EnumFromTrait; use mm2_err_handle::common_errors::WithInternal; #[cfg(target_arch = "wasm32")] @@ -24,8 +25,6 @@ pub enum EthActivationV2Error { UnreachableNodes(String), #[display(fmt = "Enable request for ETH coin must have at least 1 node")] AtLeastOneNodeRequired, - #[display(fmt = "'derivation_path' field is not found in config")] - DerivationPathIsNotSet, #[display(fmt = "Error deserializing 'derivation_path': {}", _0)] ErrorDeserializingDerivationPath(String), PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), @@ -100,6 +99,8 @@ pub struct EthActivationV2Request { pub required_confirmations: Option, #[serde(default)] pub priv_key_policy: EthPrivKeyActivationPolicy, + #[serde(default)] + pub path_to_address: StandardHDCoinAddress, } #[derive(Clone, Deserialize)] @@ -246,14 +247,25 @@ pub async fn eth_coin_from_conf_and_request_v2( } } - let (my_address, priv_key_policy) = build_address_and_priv_key_policy(conf, priv_key_policy).await?; + let (my_address, priv_key_policy) = + build_address_and_priv_key_policy(conf, priv_key_policy, &req.path_to_address).await?; let my_address_str = checksum_address(&format!("{:02x}", my_address)); let chain_id = conf["chain_id"].as_u64(); let (web3, web3_instances) = match (req.rpc_mode, &priv_key_policy) { - (EthRpcMode::Http, EthPrivKeyPolicy::KeyPair(key_pair)) => { - build_http_transport(ctx, ticker.clone(), my_address_str, key_pair, &req.nodes).await? + ( + EthRpcMode::Http, + EthPrivKeyPolicy::Iguana(key_pair) + | EthPrivKeyPolicy::HDWallet { + activated_key: key_pair, + .. + }, + ) => build_http_transport(ctx, ticker.clone(), my_address_str, key_pair, &req.nodes).await?, + (EthRpcMode::Http, EthPrivKeyPolicy::Trezor) => { + return MmError::err(EthActivationV2Error::PrivKeyPolicyNotAllowed( + PrivKeyPolicyNotAllowed::HardwareWalletNotSupported, + )); }, #[cfg(target_arch = "wasm32")] (EthRpcMode::Metamask, EthPrivKeyPolicy::Metamask(_)) => { @@ -263,7 +275,7 @@ pub async fn eth_coin_from_conf_and_request_v2( build_metamask_transport(ctx, ticker.clone(), chain_id).await? }, #[cfg(target_arch = "wasm32")] - (_, _) => { + (EthRpcMode::Http, EthPrivKeyPolicy::Metamask(_)) | (EthRpcMode::Metamask, _) => { let error = r#"priv_key_policy="Metamask" and rpc_mode="Metamask" should be used both"#.to_string(); return MmError::err(EthActivationV2Error::ActivationFailed { ticker, error }); }, @@ -322,37 +334,44 @@ pub async fn eth_coin_from_conf_and_request_v2( pub(crate) async fn build_address_and_priv_key_policy( conf: &Json, priv_key_policy: EthPrivKeyBuildPolicy, + path_to_address: &StandardHDCoinAddress, ) -> MmResult<(Address, EthPrivKeyPolicy), EthActivationV2Error> { - let raw_priv_key = match priv_key_policy { - EthPrivKeyBuildPolicy::IguanaPrivKey(iguana) => iguana, + match priv_key_policy { + EthPrivKeyBuildPolicy::IguanaPrivKey(iguana) => { + let key_pair = KeyPair::from_secret_slice(iguana.as_slice()) + .map_to_mm(|e| EthActivationV2Error::InternalError(e.to_string()))?; + Ok((key_pair.address(), EthPrivKeyPolicy::Iguana(key_pair))) + }, EthPrivKeyBuildPolicy::GlobalHDAccount(global_hd_ctx) => { // Consider storing `derivation_path` at `EthCoinImpl`. - let derivation_path: Option = json::from_value(conf["derivation_path"].clone()) + let derivation_path = json::from_value(conf["derivation_path"].clone()) .map_to_mm(|e| EthActivationV2Error::ErrorDeserializingDerivationPath(e.to_string()))?; - let derivation_path = derivation_path.or_mm_err(|| EthActivationV2Error::DerivationPathIsNotSet)?; - global_hd_ctx - .derive_secp256k1_secret(&derivation_path) - .mm_err(|e| EthActivationV2Error::InternalError(e.to_string()))? + let raw_priv_key = global_hd_ctx + .derive_secp256k1_secret(&derivation_path, path_to_address) + .mm_err(|e| EthActivationV2Error::InternalError(e.to_string()))?; + let activated_key_pair = KeyPair::from_secret_slice(raw_priv_key.as_slice()) + .map_to_mm(|e| EthActivationV2Error::InternalError(e.to_string()))?; + let bip39_secp_priv_key = global_hd_ctx.root_priv_key().clone(); + Ok((activated_key_pair.address(), EthPrivKeyPolicy::HDWallet { + derivation_path, + activated_key: activated_key_pair, + bip39_secp_priv_key, + })) }, #[cfg(target_arch = "wasm32")] EthPrivKeyBuildPolicy::Metamask(metamask_ctx) => { let address = *metamask_ctx.check_active_eth_account().await?; let public_key_uncompressed = metamask_ctx.eth_account_pubkey_uncompressed(); let public_key = compress_public_key(public_key_uncompressed)?; - return Ok(( + Ok(( address, EthPrivKeyPolicy::Metamask(EthMetamaskPolicy { public_key, public_key_uncompressed, }), - )); + )) }, - }; - - let key_pair = KeyPair::from_secret_slice(raw_priv_key.as_slice()) - .map_to_mm(|e| EthActivationV2Error::InternalError(e.to_string()))?; - let address = key_pair.address(); - Ok((address, EthPrivKeyPolicy::KeyPair(key_pair))) + } } async fn build_http_transport( diff --git a/mm2src/coins/lightning/ln_events.rs b/mm2src/coins/lightning/ln_events.rs index cb66cf8c5e..a90b13be82 100644 --- a/mm2src/coins/lightning/ln_events.rs +++ b/mm2src/coins/lightning/ln_events.rs @@ -216,7 +216,7 @@ fn sign_funding_transaction( let key_pair = coin .as_ref() .priv_key_policy - .key_pair_or_err() + .activated_key_or_err() .map_err(|e| SignFundingTransactionError::Internal(e.to_string()))?; let prev_script = Builder::build_p2pkh(&my_address.hash); diff --git a/mm2src/coins/lightning/ln_utils.rs b/mm2src/coins/lightning/ln_utils.rs index 8ab92629e7..88af1d68cc 100644 --- a/mm2src/coins/lightning/ln_utils.rs +++ b/mm2src/coins/lightning/ln_utils.rs @@ -92,7 +92,7 @@ pub fn init_keys_manager(platform: &Platform) -> EnableLightningResult for UnexpectedDerivationMethod { + fn from(e: PrivKeyPolicyNotAllowed) -> Self { + match e { + PrivKeyPolicyNotAllowed::HardwareWalletNotSupported => UnexpectedDerivationMethod::Trezor, + PrivKeyPolicyNotAllowed::UnsupportedMethod(method) => UnexpectedDerivationMethod::UnsupportedError(method), + PrivKeyPolicyNotAllowed::InternalError(e) => UnexpectedDerivationMethod::InternalError(e), + } + } } pub trait Transaction: fmt::Debug + 'static { @@ -1234,10 +1258,9 @@ pub trait GetWithdrawSenderAddress { ) -> MmResult, WithdrawError>; } -#[derive(Clone, Deserialize)] +#[derive(Clone, Deserialize, Serialize)] #[serde(untagged)] pub enum WithdrawFrom { - // AccountId { account_id: u32 }, AddressId(HDAccountAddressId), /// Don't use `Bip44DerivationPath` or `RpcDerivationPath` because if there is an error in the path, /// `serde::Deserialize` returns "data did not match any variant of untagged enum WithdrawFrom". @@ -1245,6 +1268,7 @@ pub enum WithdrawFrom { DerivationPath { derivation_path: String, }, + HDWalletAddress(StandardHDCoinAddress), } #[derive(Clone, Deserialize)] @@ -2030,6 +2054,8 @@ pub enum WithdrawError { #[from_stringify("NumConversError", "UnexpectedDerivationMethod", "PrivKeyPolicyNotAllowed")] #[display(fmt = "Internal error: {}", _0)] InternalError(String), + #[display(fmt = "Unsupported error: {}", _0)] + UnsupportedError(String), #[display(fmt = "{} coin doesn't support NFT withdrawing", coin)] CoinDoesntSupportNftWithdraw { coin: String, @@ -2078,6 +2104,7 @@ impl HttpStatusCode for WithdrawError { | WithdrawError::UnexpectedFromAddress(_) | WithdrawError::UnknownAccount { .. } | WithdrawError::UnexpectedUserAction { .. } + | WithdrawError::UnsupportedError(_) | WithdrawError::ActionNotAllowed(_) | WithdrawError::GetNftInfoError(_) | WithdrawError::AddressMismatchError { .. } @@ -2148,6 +2175,13 @@ impl From for WithdrawError { } } +impl From for WithdrawError { + fn from(e: Bip32Error) -> Self { + let error = format!("Error deriving key: {}", e); + WithdrawError::InternalError(error) + } +} + impl WithdrawError { /// Construct [`WithdrawError`] from [`GenerateTxError`] using additional `coin` and `decimals`. pub fn from_generate_tx_error(gen_tx_err: GenerateTxError, coin: String, decimals: u8) -> WithdrawError { @@ -2838,23 +2872,103 @@ impl Default for PrivKeyActivationPolicy { fn default() -> Self { PrivKeyActivationPolicy::ContextPrivKey } } -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum PrivKeyPolicy { - KeyPair(T), + Iguana(T), + HDWallet { + /// Derivation path of the coin. + /// This derivation path consists of `purpose` and `coin_type` only + /// where the full `BIP44` address has the following structure: + /// `m/purpose'/coin_type'/account'/change/address_index`. + derivation_path: StandardHDPathToCoin, + activated_key: T, + bip39_secp_priv_key: ExtendedPrivateKey, + }, Trezor, + #[cfg(target_arch = "wasm32")] + Metamask(EthMetamaskPolicy), +} + +#[cfg(target_arch = "wasm32")] +#[derive(Clone, Debug)] +pub struct EthMetamaskPolicy { + pub(crate) public_key: EthH264, + pub(crate) public_key_uncompressed: EthH520, +} + +impl From for PrivKeyPolicy { + fn from(key_pair: T) -> Self { PrivKeyPolicy::Iguana(key_pair) } } impl PrivKeyPolicy { - pub fn key_pair(&self) -> Option<&T> { + fn activated_key(&self) -> Option<&T> { match self { - PrivKeyPolicy::KeyPair(key_pair) => Some(key_pair), + PrivKeyPolicy::Iguana(key_pair) => Some(key_pair), + PrivKeyPolicy::HDWallet { + activated_key: activated_key_pair, + .. + } => Some(activated_key_pair), PrivKeyPolicy::Trezor => None, + #[cfg(target_arch = "wasm32")] + PrivKeyPolicy::Metamask(_) => None, } } - pub fn key_pair_or_err(&self) -> Result<&T, MmError> { - self.key_pair() - .or_mm_err(|| PrivKeyPolicyNotAllowed::HardwareWalletNotSupported) + fn activated_key_or_err(&self) -> Result<&T, MmError> { + self.activated_key().or_mm_err(|| { + PrivKeyPolicyNotAllowed::UnsupportedMethod( + "`activated_key_or_err` is supported only for `PrivKeyPolicy::KeyPair` or `PrivKeyPolicy::HDWallet`" + .to_string(), + ) + }) + } + + fn bip39_secp_priv_key(&self) -> Option<&ExtendedPrivateKey> { + match self { + PrivKeyPolicy::HDWallet { + bip39_secp_priv_key, .. + } => Some(bip39_secp_priv_key), + PrivKeyPolicy::Iguana(_) | PrivKeyPolicy::Trezor => None, + #[cfg(target_arch = "wasm32")] + PrivKeyPolicy::Metamask(_) => None, + } + } + + fn bip39_secp_priv_key_or_err( + &self, + ) -> Result<&ExtendedPrivateKey, MmError> { + self.bip39_secp_priv_key().or_mm_err(|| { + PrivKeyPolicyNotAllowed::UnsupportedMethod( + "`bip39_secp_priv_key_or_err` is supported only for `PrivKeyPolicy::HDWallet`".to_string(), + ) + }) + } + + fn derivation_path(&self) -> Option<&StandardHDPathToCoin> { + match self { + PrivKeyPolicy::HDWallet { derivation_path, .. } => Some(derivation_path), + PrivKeyPolicy::Iguana(_) | PrivKeyPolicy::Trezor => None, + #[cfg(target_arch = "wasm32")] + PrivKeyPolicy::Metamask(_) => None, + } + } + + fn derivation_path_or_err(&self) -> Result<&StandardHDPathToCoin, MmError> { + self.derivation_path().or_mm_err(|| { + PrivKeyPolicyNotAllowed::UnsupportedMethod( + "`derivation_path_or_err` is supported only for `PrivKeyPolicy::HDWallet`".to_string(), + ) + }) + } + + fn hd_wallet_derived_priv_key_or_err( + &self, + path_to_address: &StandardHDCoinAddress, + ) -> Result> { + let bip39_secp_priv_key = self.bip39_secp_priv_key_or_err()?; + let derivation_path = self.derivation_path_or_err()?; + derive_secp256k1_secret(bip39_secp_priv_key.clone(), derivation_path, path_to_address) + .mm_err(|e| PrivKeyPolicyNotAllowed::InternalError(e.to_string())) } } @@ -4035,13 +4149,13 @@ pub trait RpcCommonOps { /// Currently supports only coins with `ETH` protocol type. pub async fn get_my_address(ctx: MmArc, req: MyAddressReq) -> MmResult { let ticker = req.coin.as_str(); - let coins_en = coin_conf(&ctx, ticker); - coins_conf_check(&ctx, &coins_en, ticker, None).map_to_mm(GetMyAddressError::CoinsConfCheckError)?; + let conf = coin_conf(&ctx, ticker); + coins_conf_check(&ctx, &conf, ticker, None).map_to_mm(GetMyAddressError::CoinsConfCheckError)?; - let protocol: CoinProtocol = json::from_value(coins_en["protocol"].clone())?; + let protocol: CoinProtocol = json::from_value(conf["protocol"].clone())?; let my_address = match protocol { - CoinProtocol::ETH => get_eth_address(&ctx, ticker).await?, + CoinProtocol::ETH => get_eth_address(&ctx, &conf, ticker, &req.path_to_address).await?, _ => { return MmError::err(GetMyAddressError::CoinIsNotSupported(format!( "{} doesn't support get_my_address", diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index e679947517..c84d0cbe24 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -8,7 +8,7 @@ pub(crate) mod storage; #[cfg(any(test, target_arch = "wasm32"))] mod nft_tests; -use crate::{get_my_address, MyAddressReq, WithdrawError}; +use crate::{coin_conf, get_my_address, MyAddressReq, WithdrawError}; use nft_errors::{GetInfoFromUriError, GetNftInfoError, UpdateNftError}; use nft_structs::{Chain, ContractType, ConvertChain, Nft, NftFromMoralis, NftList, NftListReq, NftMetadataReq, NftTransferHistory, NftTransferHistoryFromMoralis, NftTransfersReq, NftsTransferHistoryList, @@ -20,6 +20,7 @@ use crate::nft::nft_structs::{NftCommon, NftCtx, NftTransferCommon, RefreshMetad UriMeta}; use crate::nft::storage::{NftListStorageOps, NftStorageBuilder, NftTransferHistoryStorageOps}; use common::{parse_rfc3339_to_timestamp, APPLICATION_JSON}; +use crypto::StandardHDCoinAddress; use ethereum_types::Address; use http::header::ACCEPT; use mm2_err_handle::map_to_mm::MapToMmResult; @@ -213,7 +214,9 @@ pub async fn refresh_nft_metadata(ctx: MmArc, req: RefreshMetadataReq) -> MmResu async fn get_moralis_nft_list(ctx: &MmArc, chain: &Chain, url: &Url) -> MmResult, GetNftInfoError> { let mut res_list = Vec::new(); - let my_address = get_eth_address(ctx, &chain.to_ticker()).await?; + let ticker = chain.to_ticker(); + let conf = coin_conf(ctx, &ticker); + let my_address = get_eth_address(ctx, &conf, &ticker, &StandardHDCoinAddress::default()).await?; let mut uri_without_cursor = url.clone(); uri_without_cursor.set_path(MORALIS_API_ENDPOINT); @@ -265,7 +268,9 @@ async fn get_moralis_nft_transfers( url: &Url, ) -> MmResult, GetNftInfoError> { let mut res_list = Vec::new(); - let my_address = get_eth_address(ctx, &chain.to_ticker()).await?; + let ticker = chain.to_ticker(); + let conf = coin_conf(ctx, &ticker); + let my_address = get_eth_address(ctx, &conf, &ticker, &StandardHDCoinAddress::default()).await?; let mut uri_without_cursor = url.clone(); uri_without_cursor.set_path(MORALIS_API_ENDPOINT); @@ -501,6 +506,7 @@ async fn update_nft_list( let transfers = storage.get_transfers_from_block(chain, scan_from_block).await?; let req = MyAddressReq { coin: chain.to_ticker(), + path_to_address: StandardHDCoinAddress::default(), }; let my_address = get_my_address(ctx.clone(), req).await?.wallet_address.to_lowercase(); for transfer in transfers.into_iter() { diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index dcad8c47df..c99f2d47b7 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -534,7 +534,7 @@ impl Qrc20Coin { .await?; let my_address = self.utxo.derivation_method.single_addr_or_err()?; - let key_pair = self.utxo.priv_key_policy.key_pair_or_err()?; + let key_pair = self.utxo.priv_key_policy.activated_key_or_err()?; let prev_script = ScriptBuilder::build_p2pkh(&my_address.hash); let signed = sign_tx( diff --git a/mm2src/coins/rpc_command/tendermint/ibc_withdraw.rs b/mm2src/coins/rpc_command/tendermint/ibc_withdraw.rs index eb5d29430f..037823ee66 100644 --- a/mm2src/coins/rpc_command/tendermint/ibc_withdraw.rs +++ b/mm2src/coins/rpc_command/tendermint/ibc_withdraw.rs @@ -3,11 +3,12 @@ use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::MmError; use mm2_number::BigDecimal; -use crate::{lp_coinfind_or_err, MmCoinEnum, WithdrawError, WithdrawFee, WithdrawResult}; +use crate::{lp_coinfind_or_err, MmCoinEnum, WithdrawError, WithdrawFee, WithdrawFrom, WithdrawResult}; #[derive(Clone, Deserialize)] pub struct IBCWithdrawRequest { pub(crate) ibc_source_channel: String, + pub(crate) from: Option, pub(crate) coin: String, pub(crate) to: String, #[serde(default)] diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index 12ca7379e0..1c4964f3a0 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -19,7 +19,7 @@ use base58::ToBase58; use bincode::{deserialize, serialize}; use common::executor::{abortable_queue::AbortableQueue, AbortableSystem, AbortedError}; use common::{async_blocking, now_sec}; -use crypto::StandardHDPathToCoin; +use crypto::{StandardHDCoinAddress, StandardHDPathToCoin}; use derive_more::Display; use futures::{FutureExt, TryFutureExt}; use futures01::Future; @@ -139,6 +139,8 @@ impl From for WithdrawError { pub struct SolanaActivationParams { confirmation_commitment: CommitmentLevel, client_url: String, + #[serde(default)] + path_to_address: StandardHDCoinAddress, } #[derive(Debug, Display)] @@ -187,7 +189,7 @@ pub async fn solana_coin_with_policy( PrivKeyBuildPolicy::IguanaPrivKey(priv_key) => priv_key, PrivKeyBuildPolicy::GlobalHDAccount(global_hd) => { let derivation_path: StandardHDPathToCoin = try_s!(json::from_value(conf["derivation_path"].clone())); - try_s!(global_hd.derive_secp256k1_secret(&derivation_path)) + try_s!(global_hd.derive_secp256k1_secret(&derivation_path, ¶ms.path_to_address)) }, PrivKeyBuildPolicy::Trezor => return ERR!("{}", PrivKeyPolicyNotAllowed::HardwareWalletNotSupported), }; diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 09758242b9..33e48d1885 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -17,7 +17,7 @@ use crate::utxo::utxo_common::big_decimal_from_sat; use crate::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BigDecimal, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, NegotiateSwapContractAddrErr, - PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, + PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicy, PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionRes, RefundError, RefundPaymentArgs, RefundResult, RpcCommonOps, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, SignatureResult, SpendPaymentArgs, @@ -27,8 +27,8 @@ use crate::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BigDecimal, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, - WithdrawRequest}; + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFrom, + WithdrawFut, WithdrawRequest}; use async_std::prelude::FutureExt as AsyncStdFutureExt; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; @@ -50,7 +50,8 @@ use cosmrs::tendermint::chain::Id as ChainId; use cosmrs::tendermint::PublicKey; use cosmrs::tx::{self, Fee, Msg, Raw, SignDoc, SignerInfo}; use cosmrs::{AccountId, Any, Coin, Denom, ErrorReport}; -use crypto::{privkey::key_pair_from_secret, Secp256k1Secret, StandardHDPathToCoin}; +use crypto::privkey::key_pair_from_secret; +use crypto::{Secp256k1Secret, StandardHDCoinAddress, StandardHDPathToCoin}; use derive_more::Display; use futures::future::try_join_all; use futures::lock::Mutex as AsyncMutex; @@ -104,6 +105,8 @@ const MIN_TIME_LOCK: i64 = 50; const ACCOUNT_SEQUENCE_ERR: &str = "incorrect account sequence"; +type TendermintPrivKeyPolicy = PrivKeyPolicy; + #[async_trait] pub trait TendermintCommons { fn platform_denom(&self) -> &Denom; @@ -222,7 +225,7 @@ pub struct TendermintCoinImpl { /// My address pub account_id: AccountId, pub(super) account_prefix: String, - priv_key: Vec, + pub(super) priv_key_policy: TendermintPrivKeyPolicy, pub(crate) decimals: u8, pub(super) denom: Denom, chain_id: ChainId, @@ -262,8 +265,14 @@ pub enum TendermintInitErrorKind { InvalidDenom(String), #[display(fmt = "'derivation_path' field is not found in config")] DerivationPathIsNotSet, + #[display(fmt = "'account' field is not found in config")] + AccountIsNotSet, + #[display(fmt = "'address_index' field is not found in config")] + AddressIndexIsNotSet, #[display(fmt = "Error deserializing 'derivation_path': {}", _0)] ErrorDeserializingDerivationPath(String), + #[display(fmt = "Error deserializing 'path_to_address': {}", _0)] + ErrorDeserializingPathToAddress(String), PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), RpcError(String), #[display(fmt = "avg_blocktime is missing in coin configuration")] @@ -285,6 +294,10 @@ impl From for TendermintCoinRpcError { fn from(err: DecodeError) -> Self { TendermintCoinRpcError::Prost(err) } } +impl From for TendermintCoinRpcError { + fn from(err: PrivKeyPolicyNotAllowed) -> Self { TendermintCoinRpcError::InternalError(err.to_string()) } +} + impl From for WithdrawError { fn from(err: TendermintCoinRpcError) -> Self { WithdrawError::Transport(err.to_string()) } } @@ -346,7 +359,7 @@ impl crate::Transaction for CosmosTransaction { } } -fn account_id_from_privkey(priv_key: &[u8], prefix: &str) -> MmResult { +pub(crate) fn account_id_from_privkey(priv_key: &[u8], prefix: &str) -> MmResult { let signing_key = SigningKey::from_bytes(priv_key).map_to_mm(|e| TendermintInitErrorKind::InvalidPrivKey(e.to_string()))?; @@ -421,7 +434,9 @@ impl TendermintCommons for TendermintCoin { } async fn all_balances(&self) -> MmResult { - let platform_balance_denom = self.balance_for_denom(self.denom.to_string()).await?; + let platform_balance_denom = self + .account_balance_for_denom(&self.account_id, self.denom.to_string()) + .await?; let platform_balance = big_decimal_from_sat_unsigned(platform_balance_denom, self.decimals); let ibc_assets_info = self.tokens_info.lock().clone(); @@ -429,7 +444,7 @@ impl TendermintCommons for TendermintCoin { for (ticker, info) in ibc_assets_info { let fut = async move { let balance_denom = self - .balance_for_denom(info.denom.to_string()) + .account_balance_for_denom(&self.account_id, info.denom.to_string()) .await .map_err(|e| e.into_inner())?; let balance_decimal = big_decimal_from_sat_unsigned(balance_denom, info.decimals); @@ -459,7 +474,7 @@ impl TendermintCoin { protocol_info: TendermintProtocolInfo, rpc_urls: Vec, tx_history: bool, - priv_key_policy: PrivKeyBuildPolicy, + priv_key_policy: TendermintPrivKeyPolicy, ) -> MmResult { if rpc_urls.is_empty() { return MmError::err(TendermintInitError { @@ -468,7 +483,10 @@ impl TendermintCoin { }); } - let priv_key = secret_from_priv_key_policy(&conf, &ticker, priv_key_policy)?; + let priv_key = priv_key_policy.activated_key_or_err().mm_err(|e| TendermintInitError { + ticker: ticker.clone(), + kind: TendermintInitErrorKind::Internal(e.to_string()), + })?; let account_id = account_id_from_privkey(priv_key.as_slice(), &protocol_info.account_prefix).mm_err(|kind| { @@ -515,7 +533,7 @@ impl TendermintCoin { ticker, account_id, account_prefix: protocol_info.account_prefix, - priv_key: priv_key.to_vec(), + priv_key_policy, decimals: protocol_info.decimals, denom, chain_id, @@ -535,8 +553,26 @@ impl TendermintCoin { let to_address = AccountId::from_str(&req.to).map_to_mm(|e| WithdrawError::InvalidAddress(e.to_string()))?; + let (account_id, priv_key) = match req.from { + Some(WithdrawFrom::HDWalletAddress(ref path_to_address)) => { + let priv_key = coin + .priv_key_policy + .hd_wallet_derived_priv_key_or_err(path_to_address)?; + let account_id = account_id_from_privkey(priv_key.as_slice(), &coin.account_prefix) + .map_err(|e| WithdrawError::InternalError(e.to_string()))?; + (account_id, priv_key) + }, + Some(WithdrawFrom::AddressId(_)) | Some(WithdrawFrom::DerivationPath { .. }) => { + return MmError::err(WithdrawError::UnexpectedFromAddress( + "Withdraw from 'AddressId' or 'DerivationPath' is not supported yet for Tendermint!" + .to_string(), + )) + }, + None => (coin.account_id.clone(), *coin.priv_key_policy.activated_key_or_err()?), + }; + let (balance_denom, balance_dec) = coin - .get_balance_as_unsigned_and_decimal(&coin.denom, coin.decimals()) + .get_balance_as_unsigned_and_decimal(&account_id, &coin.denom, coin.decimals()) .await?; // << BEGIN TX SIMULATION FOR FEE CALCULATION @@ -554,7 +590,7 @@ impl TendermintCoin { }); } - let received_by_me = if to_address == coin.account_id { + let received_by_me = if to_address == account_id { amount_dec } else { BigDecimal::default() @@ -564,7 +600,7 @@ impl TendermintCoin { let msg_transfer = MsgTransfer::new_with_default_timeout( req.ibc_source_channel.clone(), - coin.account_id.clone(), + account_id.clone(), to_address.clone(), Coin { denom: coin.denom.clone(), @@ -586,7 +622,14 @@ impl TendermintCoin { let (_, gas_limit) = coin.gas_info_for_withdraw(&req.fee, IBC_GAS_LIMIT_DEFAULT); let fee_amount_u64 = coin - .calculate_fee_amount_as_u64(msg_transfer.clone(), timeout_height, memo.clone(), req.fee) + .calculate_account_fee_amount_as_u64( + &account_id, + &priv_key, + msg_transfer.clone(), + timeout_height, + memo.clone(), + req.fee, + ) .await?; let fee_amount_dec = big_decimal_from_sat_unsigned(fee_amount_u64, coin.decimals()); @@ -622,7 +665,7 @@ impl TendermintCoin { let msg_transfer = MsgTransfer::new_with_default_timeout( req.ibc_source_channel.clone(), - coin.account_id.clone(), + account_id.clone(), to_address.clone(), Coin { denom: coin.denom.clone(), @@ -632,9 +675,9 @@ impl TendermintCoin { .to_any() .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; - let account_info = coin.my_account_info().await?; + let account_info = coin.account_info(&account_id).await?; let tx_raw = coin - .any_to_signed_raw_tx(account_info, msg_transfer, fee, timeout_height, memo.clone()) + .any_to_signed_raw_tx(&priv_key, account_info, msg_transfer, fee, timeout_height, memo.clone()) .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; let tx_bytes = tx_raw @@ -645,7 +688,7 @@ impl TendermintCoin { Ok(TransactionDetails { tx_hash: hex::encode_upper(hash.as_slice()), tx_hex: tx_bytes.into(), - from: vec![coin.account_id.to_string()], + from: vec![account_id.to_string()], to: vec![req.to], my_balance_change: &received_by_me - &total_amount, spent_by_me: total_amount.clone(), @@ -794,6 +837,7 @@ impl TendermintCoin { pub(super) fn gen_simulated_tx( &self, account_info: BaseAccount, + priv_key: &Secp256k1Secret, tx_payload: Any, timeout_height: u64, memo: String, @@ -805,7 +849,7 @@ impl TendermintCoin { let fee = Fee::from_amount_and_gas(fee_amount, GAS_LIMIT_DEFAULT); - let signkey = SigningKey::from_bytes(&self.priv_key)?; + let signkey = SigningKey::from_bytes(priv_key.as_slice())?; let tx_body = tx::Body::new(vec![tx_payload], memo, timeout_height as u32); let auth_info = SignerInfo::single_direct(Some(signkey.public_key()), account_info.sequence).auth_info(fee); let sign_doc = SignDoc::new(&tx_body, &auth_info, &self.chain_id, account_info.account_number)?; @@ -850,7 +894,8 @@ impl TendermintCoin { ) -> Result<(String, Raw), TransactionErr> { let (tx_id, tx_raw) = loop { let tx_raw = try_tx_s!(self.any_to_signed_raw_tx( - try_tx_s!(self.my_account_info().await), + try_tx_s!(self.priv_key_policy.activated_key_or_err()), + try_tx_s!(self.account_info(&self.account_id).await), tx_payload.clone(), fee.clone(), timeout_height, @@ -884,9 +929,16 @@ impl TendermintCoin { let path = AbciPath::from_str(ABCI_SIMULATE_TX_PATH).expect("valid path"); let (response, raw_response) = loop { - let account_info = self.my_account_info().await?; + let account_info = self.account_info(&self.account_id).await?; + let activated_priv_key = self.priv_key_policy.activated_key_or_err()?; let tx_bytes = self - .gen_simulated_tx(account_info, msg.clone(), timeout_height, memo.clone()) + .gen_simulated_tx( + account_info, + activated_priv_key, + msg.clone(), + timeout_height, + memo.clone(), + ) .map_to_mm(|e| TendermintCoinRpcError::InternalError(format!("{}", e)))?; let request = AbciRequest::new( @@ -939,8 +991,10 @@ impl TendermintCoin { } #[allow(deprecated)] - pub(super) async fn calculate_fee_amount_as_u64( + pub(super) async fn calculate_account_fee_amount_as_u64( &self, + account_id: &AccountId, + priv_key: &Secp256k1Secret, msg: Any, timeout_height: u64, memo: String, @@ -949,9 +1003,9 @@ impl TendermintCoin { let path = AbciPath::from_str(ABCI_SIMULATE_TX_PATH).expect("valid path"); let (response, raw_response) = loop { - let account_info = self.my_account_info().await?; + let account_info = self.account_info(account_id).await?; let tx_bytes = self - .gen_simulated_tx(account_info, msg.clone(), timeout_height, memo.clone()) + .gen_simulated_tx(account_info, priv_key, msg.clone(), timeout_height, memo.clone()) .map_to_mm(|e| TendermintCoinRpcError::InternalError(format!("{}", e)))?; let request = AbciRequest::new( @@ -996,10 +1050,10 @@ impl TendermintCoin { Ok(((gas.gas_used as f64 * 1.5) * gas_price).ceil() as u64) } - pub(super) async fn my_account_info(&self) -> MmResult { + pub(super) async fn account_info(&self, account_id: &AccountId) -> MmResult { let path = AbciPath::from_str(ABCI_QUERY_ACCOUNT_PATH).expect("valid path"); let request = QueryAccountRequest { - address: self.account_id.to_string(), + address: account_id.to_string(), }; let request = AbciRequest::new( Some(path), @@ -1031,10 +1085,14 @@ impl TendermintCoin { Ok(base_account) } - pub(super) async fn balance_for_denom(&self, denom: String) -> MmResult { + pub(super) async fn account_balance_for_denom( + &self, + account_id: &AccountId, + denom: String, + ) -> MmResult { let path = AbciPath::from_str(ABCI_QUERY_BALANCE_PATH).expect("valid path"); let request = QueryBalanceRequest { - address: self.account_id.to_string(), + address: account_id.to_string(), denom, }; let request = AbciRequest::new( @@ -1103,13 +1161,14 @@ impl TendermintCoin { pub(super) fn any_to_signed_raw_tx( &self, + priv_key: &Secp256k1Secret, account_info: BaseAccount, tx_payload: Any, fee: Fee, timeout_height: u64, memo: String, ) -> cosmrs::Result { - let signkey = SigningKey::from_bytes(&self.priv_key)?; + let signkey = SigningKey::from_bytes(priv_key.as_slice())?; let tx_body = tx::Body::new(vec![tx_payload], memo, timeout_height as u32); let auth_info = SignerInfo::single_direct(Some(signkey.public_key()), account_info.sequence).auth_info(fee); let sign_doc = SignDoc::new(&tx_body, &auth_info, &self.chain_id, account_info.account_number)?; @@ -1524,7 +1583,11 @@ impl TendermintCoin { let timeout_height = current_block + TIMEOUT_HEIGHT_DELTA; let fee_uamount = self - .calculate_fee_amount_as_u64( + .calculate_account_fee_amount_as_u64( + &self.account_id, + self.priv_key_policy + .activated_key_or_err() + .mm_err(|e| TradePreimageError::InternalError(e.to_string()))?, create_htlc_tx.msg_payload.clone(), timeout_height, TX_DEFAULT_MEMO.to_owned(), @@ -1573,7 +1636,16 @@ impl TendermintCoin { .map_err(|e| MmError::new(TradePreimageError::InternalError(e.to_string())))?; let fee_uamount = self - .calculate_fee_amount_as_u64(msg_send.clone(), timeout_height, TX_DEFAULT_MEMO.to_owned(), None) + .calculate_account_fee_amount_as_u64( + &self.account_id, + self.priv_key_policy + .activated_key_or_err() + .mm_err(|e| TradePreimageError::InternalError(e.to_string()))?, + msg_send, + timeout_height, + TX_DEFAULT_MEMO.to_owned(), + None, + ) .await?; let fee_amount = big_decimal_from_sat_unsigned(fee_uamount, decimals); @@ -1586,10 +1658,11 @@ impl TendermintCoin { pub(super) async fn get_balance_as_unsigned_and_decimal( &self, + account_id: &AccountId, denom: &Denom, decimals: u8, ) -> MmResult<(u64, BigDecimal), TendermintCoinRpcError> { - let denom_ubalance = self.balance_for_denom(denom.to_string()).await?; + let denom_ubalance = self.account_balance_for_denom(account_id, denom.to_string()).await?; let denom_balance_dec = big_decimal_from_sat_unsigned(denom_ubalance, decimals); Ok((denom_ubalance, denom_balance_dec)) @@ -1832,8 +1905,26 @@ impl MmCoin for TendermintCoin { ))); } + let (account_id, priv_key) = match req.from { + Some(WithdrawFrom::HDWalletAddress(ref path_to_address)) => { + let priv_key = coin + .priv_key_policy + .hd_wallet_derived_priv_key_or_err(path_to_address)?; + let account_id = account_id_from_privkey(priv_key.as_slice(), &coin.account_prefix) + .map_err(|e| WithdrawError::InternalError(e.to_string()))?; + (account_id, priv_key) + }, + Some(WithdrawFrom::AddressId(_)) | Some(WithdrawFrom::DerivationPath { .. }) => { + return MmError::err(WithdrawError::UnexpectedFromAddress( + "Withdraw from 'AddressId' or 'DerivationPath' is not supported yet for Tendermint!" + .to_string(), + )) + }, + None => (coin.account_id.clone(), *coin.priv_key_policy.activated_key_or_err()?), + }; + let (balance_denom, balance_dec) = coin - .get_balance_as_unsigned_and_decimal(&coin.denom, coin.decimals()) + .get_balance_as_unsigned_and_decimal(&account_id, &coin.denom, coin.decimals()) .await?; // << BEGIN TX SIMULATION FOR FEE CALCULATION @@ -1853,14 +1944,14 @@ impl MmCoin for TendermintCoin { }); } - let received_by_me = if to_address == coin.account_id { + let received_by_me = if to_address == account_id { amount_dec } else { BigDecimal::default() }; let msg_send = MsgSend { - from_address: coin.account_id.clone(), + from_address: account_id.clone(), to_address: to_address.clone(), amount: vec![Coin { denom: coin.denom.clone(), @@ -1883,7 +1974,14 @@ impl MmCoin for TendermintCoin { let (_, gas_limit) = coin.gas_info_for_withdraw(&req.fee, GAS_LIMIT_DEFAULT); let fee_amount_u64 = coin - .calculate_fee_amount_as_u64(msg_send.clone(), timeout_height, memo.clone(), req.fee) + .calculate_account_fee_amount_as_u64( + &account_id, + &priv_key, + msg_send, + timeout_height, + memo.clone(), + req.fee, + ) .await?; let fee_amount_dec = big_decimal_from_sat_unsigned(fee_amount_u64, coin.decimals()); @@ -1918,7 +2016,7 @@ impl MmCoin for TendermintCoin { }; let msg_send = MsgSend { - from_address: coin.account_id.clone(), + from_address: account_id.clone(), to_address, amount: vec![Coin { denom: coin.denom.clone(), @@ -1928,9 +2026,9 @@ impl MmCoin for TendermintCoin { .to_any() .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; - let account_info = coin.my_account_info().await?; + let account_info = coin.account_info(&account_id).await?; let tx_raw = coin - .any_to_signed_raw_tx(account_info, msg_send, fee, timeout_height, memo.clone()) + .any_to_signed_raw_tx(&priv_key, account_info, msg_send, fee, timeout_height, memo.clone()) .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; let tx_bytes = tx_raw @@ -1942,7 +2040,7 @@ impl MmCoin for TendermintCoin { Ok(TransactionDetails { tx_hash: hex::encode_upper(hash.as_slice()), tx_hex: tx_bytes.into(), - from: vec![coin.account_id.to_string()], + from: vec![account_id.to_string()], to: vec![req.to], my_balance_change: &received_by_me - &total_amount, spent_by_me: total_amount.clone(), @@ -2105,7 +2203,8 @@ impl MarketCoinOps for TendermintCoin { fn my_address(&self) -> MmResult { Ok(self.account_id.to_string()) } fn get_public_key(&self) -> Result> { - let key = SigningKey::from_bytes(&self.priv_key).expect("privkey validity is checked on coin creation"); + let key = SigningKey::from_bytes(self.priv_key_policy.activated_key_or_err()?.as_slice()) + .expect("privkey validity is checked on coin creation"); Ok(key.public_key().to_string()) } @@ -2127,7 +2226,9 @@ impl MarketCoinOps for TendermintCoin { fn my_balance(&self) -> BalanceFut { let coin = self.clone(); let fut = async move { - let balance_denom = coin.balance_for_denom(coin.denom.to_string()).await?; + let balance_denom = coin + .account_balance_for_denom(&coin.account_id, coin.denom.to_string()) + .await?; Ok(CoinBalance { spendable: big_decimal_from_sat_unsigned(balance_denom, coin.decimals), unspendable: BigDecimal::default(), @@ -2285,7 +2386,13 @@ impl MarketCoinOps for TendermintCoin { Box::new(fut.boxed().compat()) } - fn display_priv_key(&self) -> Result { Ok(hex::encode(&self.priv_key)) } + fn display_priv_key(&self) -> Result { + Ok(self + .priv_key_policy + .activated_key_or_err() + .map_err(|e| e.to_string())? + .to_string()) + } fn min_tx_amount(&self) -> BigDecimal { big_decimal_from_sat(MIN_TX_SATOSHIS, self.decimals) } @@ -2524,7 +2631,13 @@ impl SwapOps for TendermintCoin { #[inline] fn derive_htlc_key_pair(&self, swap_unique_data: &[u8]) -> KeyPair { - key_pair_from_secret(&self.priv_key).expect("valid priv key") + key_pair_from_secret( + self.priv_key_policy + .activated_key_or_err() + .expect("valid priv key") + .as_ref(), + ) + .expect("valid priv key") } #[inline] @@ -2652,27 +2765,34 @@ impl WatcherOps for TendermintCoin { } } -/// Processes the given `priv_key_policy` and returns corresponding `Secp256k1Secret`. +/// Processes the given `priv_key_build_policy` and returns corresponding `TendermintPrivKeyPolicy`. /// This function expects either [`PrivKeyBuildPolicy::IguanaPrivKey`] /// or [`PrivKeyBuildPolicy::GlobalHDAccount`], otherwise returns `PrivKeyPolicyNotAllowed` error. -pub(crate) fn secret_from_priv_key_policy( +pub fn tendermint_priv_key_policy( conf: &TendermintConf, ticker: &str, - priv_key_policy: PrivKeyBuildPolicy, -) -> MmResult { - match priv_key_policy { - PrivKeyBuildPolicy::IguanaPrivKey(iguana) => Ok(iguana), + priv_key_build_policy: PrivKeyBuildPolicy, + path_to_address: StandardHDCoinAddress, +) -> MmResult { + match priv_key_build_policy { + PrivKeyBuildPolicy::IguanaPrivKey(iguana) => Ok(TendermintPrivKeyPolicy::Iguana(iguana)), PrivKeyBuildPolicy::GlobalHDAccount(global_hd) => { let derivation_path = conf.derivation_path.as_ref().or_mm_err(|| TendermintInitError { ticker: ticker.to_string(), kind: TendermintInitErrorKind::DerivationPathIsNotSet, })?; - global_hd - .derive_secp256k1_secret(derivation_path) + let activated_priv_key = global_hd + .derive_secp256k1_secret(derivation_path, &path_to_address) .mm_err(|e| TendermintInitError { ticker: ticker.to_string(), kind: TendermintInitErrorKind::InvalidPrivKey(e.to_string()), - }) + })?; + let bip39_secp_priv_key = global_hd.root_priv_key().clone(); + Ok(TendermintPrivKeyPolicy::HDWallet { + derivation_path: derivation_path.clone(), + activated_key: activated_priv_key, + bip39_secp_priv_key, + }) }, PrivKeyBuildPolicy::Trezor => { let kind = @@ -2777,7 +2897,7 @@ pub mod tendermint_coin_tests { }; let key_pair = key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap(); - let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(key_pair.private().secret); + let priv_key_policy = TendermintPrivKeyPolicy::Iguana(key_pair.private().secret); let coin = block_on(TendermintCoin::init( &ctx, @@ -2889,7 +3009,7 @@ pub mod tendermint_coin_tests { }; let key_pair = key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap(); - let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(key_pair.private().secret); + let priv_key_policy = TendermintPrivKeyPolicy::Iguana(key_pair.private().secret); let coin = block_on(TendermintCoin::init( &ctx, @@ -2947,7 +3067,7 @@ pub mod tendermint_coin_tests { }; let key_pair = key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap(); - let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(key_pair.private().secret); + let priv_key_policy = TendermintPrivKeyPolicy::Iguana(key_pair.private().secret); let coin = block_on(TendermintCoin::init( &ctx, @@ -3019,7 +3139,7 @@ pub mod tendermint_coin_tests { }; let key_pair = key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap(); - let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(key_pair.private().secret); + let priv_key_policy = TendermintPrivKeyPolicy::Iguana(key_pair.private().secret); let coin = block_on(TendermintCoin::init( &ctx, @@ -3213,7 +3333,7 @@ pub mod tendermint_coin_tests { }; let key_pair = key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap(); - let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(key_pair.private().secret); + let priv_key_policy = TendermintPrivKeyPolicy::Iguana(key_pair.private().secret); let coin = block_on(TendermintCoin::init( &ctx, @@ -3297,7 +3417,7 @@ pub mod tendermint_coin_tests { }; let key_pair = key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap(); - let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(key_pair.private().secret); + let priv_key_policy = TendermintPrivKeyPolicy::Iguana(key_pair.private().secret); let coin = block_on(TendermintCoin::init( &ctx, @@ -3371,7 +3491,7 @@ pub mod tendermint_coin_tests { }; let key_pair = key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap(); - let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(key_pair.private().secret); + let priv_key_policy = TendermintPrivKeyPolicy::Iguana(key_pair.private().secret); let coin = block_on(TendermintCoin::init( &ctx, @@ -3441,7 +3561,7 @@ pub mod tendermint_coin_tests { let ctx = mm2_core::mm_ctx::MmCtxBuilder::default().into_mm_arc(); let key_pair = key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap(); - let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(key_pair.private().secret); + let priv_key_policy = TendermintPrivKeyPolicy::Iguana(key_pair.private().secret); let coin = common::block_on(TendermintCoin::init( &ctx, @@ -3493,7 +3613,7 @@ pub mod tendermint_coin_tests { let ctx = mm2_core::mm_ctx::MmCtxBuilder::default().into_mm_arc(); let key_pair = key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap(); - let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(key_pair.private().secret); + let priv_key_policy = TendermintPrivKeyPolicy::Iguana(key_pair.private().secret); let coin = common::block_on(TendermintCoin::init( &ctx, diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 09417c210f..f985ccec3c 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -5,6 +5,7 @@ use super::ibc::IBC_GAS_LIMIT_DEFAULT; use super::{TendermintCoin, TendermintFeeDetails, GAS_LIMIT_DEFAULT, MIN_TX_SATOSHIS, TIMEOUT_HEIGHT_DELTA, TX_DEFAULT_MEMO}; use crate::rpc_command::tendermint::IBCWithdrawRequest; +use crate::tendermint::account_id_from_privkey; use crate::utxo::utxo_common::big_decimal_from_sat; use crate::{big_decimal_from_sat_unsigned, utxo::sat_from_big_decimal, BalanceFut, BigDecimal, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, @@ -18,7 +19,7 @@ use crate::{big_decimal_from_sat_unsigned, utxo::sat_from_big_decimal, BalanceFu ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawError, WithdrawFut, WithdrawRequest}; + WithdrawError, WithdrawFrom, WithdrawFut, WithdrawRequest}; use crate::{MmCoinEnum, PaymentInstructionArgs, WatcherReward, WatcherRewardError}; use async_trait::async_trait; use bitcrypto::sha256; @@ -110,12 +111,33 @@ impl TendermintToken { let to_address = AccountId::from_str(&req.to).map_to_mm(|e| WithdrawError::InvalidAddress(e.to_string()))?; + let (account_id, priv_key) = match req.from { + Some(WithdrawFrom::HDWalletAddress(ref path_to_address)) => { + let priv_key = platform + .priv_key_policy + .hd_wallet_derived_priv_key_or_err(path_to_address)?; + let account_id = account_id_from_privkey(priv_key.as_slice(), &platform.account_prefix) + .map_err(|e| WithdrawError::InternalError(e.to_string()))?; + (account_id, priv_key) + }, + Some(WithdrawFrom::AddressId(_)) | Some(WithdrawFrom::DerivationPath { .. }) => { + return MmError::err(WithdrawError::UnexpectedFromAddress( + "Withdraw from 'AddressId' or 'DerivationPath' is not supported yet for Tendermint!" + .to_string(), + )) + }, + None => ( + platform.account_id.clone(), + *platform.priv_key_policy.activated_key_or_err()?, + ), + }; + let (base_denom_balance, base_denom_balance_dec) = platform - .get_balance_as_unsigned_and_decimal(&platform.denom, token.decimals()) + .get_balance_as_unsigned_and_decimal(&account_id, &platform.denom, token.decimals()) .await?; let (balance_denom, balance_dec) = platform - .get_balance_as_unsigned_and_decimal(&token.denom, token.decimals()) + .get_balance_as_unsigned_and_decimal(&account_id, &token.denom, token.decimals()) .await?; let (amount_denom, amount_dec, total_amount) = if req.max { @@ -147,7 +169,7 @@ impl TendermintToken { }); } - let received_by_me = if to_address == platform.account_id { + let received_by_me = if to_address == account_id { amount_dec } else { BigDecimal::default() @@ -157,7 +179,7 @@ impl TendermintToken { let msg_transfer = MsgTransfer::new_with_default_timeout( req.ibc_source_channel.clone(), - platform.account_id.clone(), + account_id.clone(), to_address.clone(), Coin { denom: token.denom.clone(), @@ -178,7 +200,14 @@ impl TendermintToken { let (_, gas_limit) = platform.gas_info_for_withdraw(&req.fee, IBC_GAS_LIMIT_DEFAULT); let fee_amount_u64 = platform - .calculate_fee_amount_as_u64(msg_transfer.clone(), timeout_height, memo.clone(), req.fee) + .calculate_account_fee_amount_as_u64( + &account_id, + &priv_key, + msg_transfer.clone(), + timeout_height, + memo.clone(), + req.fee, + ) .await?; let fee_amount_dec = big_decimal_from_sat_unsigned(fee_amount_u64, platform.decimals()); @@ -198,9 +227,9 @@ impl TendermintToken { let fee = Fee::from_amount_and_gas(fee_amount, gas_limit); - let account_info = platform.my_account_info().await?; + let account_info = platform.account_info(&account_id).await?; let tx_raw = platform - .any_to_signed_raw_tx(account_info, msg_transfer, fee, timeout_height, memo.clone()) + .any_to_signed_raw_tx(&priv_key, account_info, msg_transfer, fee, timeout_height, memo.clone()) .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; let tx_bytes = tx_raw @@ -211,7 +240,7 @@ impl TendermintToken { Ok(TransactionDetails { tx_hash: hex::encode_upper(hash.as_slice()), tx_hex: tx_bytes.into(), - from: vec![platform.account_id.to_string()], + from: vec![account_id.to_string()], to: vec![req.to], my_balance_change: &received_by_me - &total_amount, spent_by_me: total_amount.clone(), @@ -517,7 +546,10 @@ impl MarketCoinOps for TendermintToken { fn my_balance(&self) -> BalanceFut { let coin = self.clone(); let fut = async move { - let balance_denom = coin.platform_coin.balance_for_denom(coin.denom.to_string()).await?; + let balance_denom = coin + .platform_coin + .account_balance_for_denom(&coin.platform_coin.account_id, coin.denom.to_string()) + .await?; Ok(CoinBalance { spendable: big_decimal_from_sat_unsigned(balance_denom, coin.decimals), unspendable: BigDecimal::default(), @@ -588,12 +620,33 @@ impl MmCoin for TendermintToken { ))); } + let (account_id, priv_key) = match req.from { + Some(WithdrawFrom::HDWalletAddress(ref path_to_address)) => { + let priv_key = platform + .priv_key_policy + .hd_wallet_derived_priv_key_or_err(path_to_address)?; + let account_id = account_id_from_privkey(priv_key.as_slice(), &platform.account_prefix) + .map_err(|e| WithdrawError::InternalError(e.to_string()))?; + (account_id, priv_key) + }, + Some(WithdrawFrom::AddressId(_)) | Some(WithdrawFrom::DerivationPath { .. }) => { + return MmError::err(WithdrawError::UnexpectedFromAddress( + "Withdraw from 'AddressId' or 'DerivationPath' is not supported yet for Tendermint!" + .to_string(), + )) + }, + None => ( + platform.account_id.clone(), + *platform.priv_key_policy.activated_key_or_err()?, + ), + }; + let (base_denom_balance, base_denom_balance_dec) = platform - .get_balance_as_unsigned_and_decimal(&platform.denom, token.decimals()) + .get_balance_as_unsigned_and_decimal(&account_id, &platform.denom, token.decimals()) .await?; let (balance_denom, balance_dec) = platform - .get_balance_as_unsigned_and_decimal(&token.denom, token.decimals()) + .get_balance_as_unsigned_and_decimal(&account_id, &token.denom, token.decimals()) .await?; let (amount_denom, amount_dec, total_amount) = if req.max { @@ -625,14 +678,14 @@ impl MmCoin for TendermintToken { }); } - let received_by_me = if to_address == platform.account_id { + let received_by_me = if to_address == account_id { amount_dec } else { BigDecimal::default() }; let msg_send = MsgSend { - from_address: platform.account_id.clone(), + from_address: account_id.clone(), to_address, amount: vec![Coin { denom: token.denom.clone(), @@ -654,7 +707,14 @@ impl MmCoin for TendermintToken { let (_, gas_limit) = platform.gas_info_for_withdraw(&req.fee, GAS_LIMIT_DEFAULT); let fee_amount_u64 = platform - .calculate_fee_amount_as_u64(msg_send.clone(), timeout_height, memo.clone(), req.fee) + .calculate_account_fee_amount_as_u64( + &account_id, + &priv_key, + msg_send.clone(), + timeout_height, + memo.clone(), + req.fee, + ) .await?; let fee_amount_dec = big_decimal_from_sat_unsigned(fee_amount_u64, platform.decimals()); @@ -674,9 +734,9 @@ impl MmCoin for TendermintToken { let fee = Fee::from_amount_and_gas(fee_amount, gas_limit); - let account_info = platform.my_account_info().await?; + let account_info = platform.account_info(&account_id).await?; let tx_raw = platform - .any_to_signed_raw_tx(account_info, msg_send, fee, timeout_height, memo.clone()) + .any_to_signed_raw_tx(&priv_key, account_info, msg_send, fee, timeout_height, memo.clone()) .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; let tx_bytes = tx_raw @@ -687,7 +747,7 @@ impl MmCoin for TendermintToken { Ok(TransactionDetails { tx_hash: hex::encode_upper(hash.as_slice()), tx_hex: tx_bytes.into(), - from: vec![platform.account_id.to_string()], + from: vec![account_id.to_string()], to: vec![req.to], my_balance_change: &received_by_me - &total_amount, spent_by_me: total_amount.clone(), diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index 9ccf7c35f8..ab1fee5bde 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -52,7 +52,7 @@ use common::jsonrpc_client::JsonRpcError; use common::log::LogOnError; use common::{now_sec, now_sec_u32}; use crypto::{Bip32DerPathOps, Bip32Error, Bip44Chain, ChildNumber, DerivationPath, Secp256k1ExtendedPublicKey, - StandardHDPathError, StandardHDPathToAccount, StandardHDPathToCoin}; + StandardHDCoinAddress, StandardHDPathError, StandardHDPathToAccount, StandardHDPathToCoin}; use derive_more::Display; #[cfg(not(target_arch = "wasm32"))] use dirs::home_dir; use futures::channel::mpsc::{Receiver as AsyncReceiver, Sender as AsyncSender, UnboundedSender}; @@ -946,7 +946,7 @@ pub trait UtxoCommonOps: fn denominate_satoshis(&self, satoshi: i64) -> f64; - /// Get a public key that matches [`PrivKeyPolicy::KeyPair`]. + /// Get a public key that matches [`PrivKeyPolicy::Iguana`]. /// /// # Fail /// @@ -1369,6 +1369,10 @@ pub struct UtxoActivationParams { /// The flag determines whether to use mature unspent outputs *only* to generate transactions. /// https://github.com/KomodoPlatform/atomicDEX-API/issues/1181 pub check_utxo_maturity: Option, + /// This determines which Address of the HD account to be used for swaps for this UTXO coin. + /// If not specified, the first non-change address for the first account is used. + #[serde(default)] + pub path_to_address: StandardHDCoinAddress, } #[derive(Debug, Display)] @@ -1384,6 +1388,8 @@ pub enum UtxoFromLegacyReqErr { InvalidScanPolicy(json::Error), InvalidMinAddressesNumber(json::Error), InvalidPrivKeyPolicy(json::Error), + InvalidAccount(json::Error), + InvalidAddressIndex(json::Error), } impl UtxoActivationParams { @@ -1421,6 +1427,9 @@ impl UtxoActivationParams { let priv_key_policy = json::from_value::>(req["priv_key_policy"].clone()) .map_to_mm(UtxoFromLegacyReqErr::InvalidPrivKeyPolicy)? .unwrap_or(PrivKeyActivationPolicy::ContextPrivKey); + let path_to_address = json::from_value::>(req["path_to_address"].clone()) + .map_to_mm(UtxoFromLegacyReqErr::InvalidAddressIndex)? + .unwrap_or_default(); Ok(UtxoActivationParams { mode, @@ -1433,6 +1442,7 @@ impl UtxoActivationParams { enable_params, priv_key_policy, check_utxo_maturity, + path_to_address, }) } } @@ -1790,7 +1800,7 @@ where T: AsRef + UtxoTxGenerationOps + UtxoTxBroadcastOps, { let my_address = try_tx_s!(coin.as_ref().derivation_method.single_addr_or_err()); - let key_pair = try_tx_s!(coin.as_ref().priv_key_policy.key_pair_or_err()); + let key_pair = try_tx_s!(coin.as_ref().priv_key_policy.activated_key_or_err()); let mut builder = UtxoTxBuilder::new(coin) .add_available_inputs(unspents) @@ -1862,6 +1872,8 @@ pub fn address_by_conf_and_pubkey_str( enable_params: EnabledCoinBalanceParams::default(), priv_key_policy: PrivKeyActivationPolicy::ContextPrivKey, check_utxo_maturity: None, + // This will not be used since the pubkey from orderbook/etc.. will be used to generate the address + path_to_address: StandardHDCoinAddress::default(), }; let conf_builder = UtxoConfBuilder::new(conf, ¶ms, coin); let utxo_conf = try_s!(conf_builder.build()); diff --git a/mm2src/coins/utxo/qtum_delegation.rs b/mm2src/coins/utxo/qtum_delegation.rs index 6cd4ee4336..231d230c51 100644 --- a/mm2src/coins/utxo/qtum_delegation.rs +++ b/mm2src/coins/utxo/qtum_delegation.rs @@ -105,7 +105,7 @@ impl QtumDelegationOps for QtumCoin { let mut buffer = b"\x15Qtum Signed Message:\n\x28".to_vec(); buffer.append(&mut addr_hash.to_string().into_bytes()); let hashed = dhash256(&buffer); - let key_pair = self.as_ref().priv_key_policy.key_pair_or_err()?; + let key_pair = self.as_ref().priv_key_policy.activated_key_or_err()?; let signature = key_pair .private() .sign_compact(&hashed) @@ -268,7 +268,7 @@ impl QtumCoin { ) -> DelegationResult { let utxo = self.as_ref(); - let key_pair = utxo.priv_key_policy.key_pair_or_err()?; + let key_pair = utxo.priv_key_policy.activated_key_or_err()?; let my_address = utxo.derivation_method.single_addr_or_err()?; let (unspents, _) = self.get_unspent_ordered_list(my_address).await?; diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index 3e03eab7f2..340b5059aa 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -646,7 +646,7 @@ impl SlpToken { unsigned.lock_time = tx_locktime; unsigned.inputs[0].sequence = input_sequence; - let my_key_pair = self.platform_coin.as_ref().priv_key_policy.key_pair_or_err()?; + let my_key_pair = self.platform_coin.as_ref().priv_key_policy.activated_key_or_err()?; let signed_p2sh_input = p2sh_spend( &unsigned, 0, @@ -1596,7 +1596,7 @@ impl MmCoin for SlpToken { let coin = self.clone(); let fut = async move { let my_address = coin.platform_coin.as_ref().derivation_method.single_addr_or_err()?; - let key_pair = coin.platform_coin.as_ref().priv_key_policy.key_pair_or_err()?; + let key_pair = coin.platform_coin.as_ref().priv_key_policy.activated_key_or_err()?; let address = CashAddress::decode(&req.to).map_to_mm(WithdrawError::InvalidAddress)?; if address.prefix != *coin.slp_prefix() { diff --git a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs index af9ba59f8a..7a8f89b029 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs @@ -17,8 +17,8 @@ use common::executor::{abortable_queue::AbortableQueue, AbortSettings, Abortable Timer}; use common::log::{error, info, LogOnError}; use common::small_rng; -use crypto::{Bip32DerPathError, CryptoCtx, CryptoCtxError, GlobalHDAccountArc, HwWalletType, Secp256k1Secret, - StandardHDPathError, StandardHDPathToCoin}; +use crypto::{Bip32DerPathError, CryptoCtx, CryptoCtxError, GlobalHDAccountArc, HwWalletType, StandardHDPathError, + StandardHDPathToCoin}; use derive_more::Display; use futures::channel::mpsc::{channel, unbounded, Receiver as AsyncReceiver, UnboundedReceiver}; use futures::compat::Future01CompatExt; @@ -112,6 +112,10 @@ impl From for UtxoCoinBuildError { fn from(e: AbortedError) -> Self { UtxoCoinBuildError::Internal(e.to_string()) } } +impl From for UtxoCoinBuildError { + fn from(e: PrivKeyPolicyNotAllowed) -> Self { UtxoCoinBuildError::PrivKeyPolicyNotAllowed(e) } +} + #[async_trait] pub trait UtxoCoinBuilder: UtxoFieldsWithIguanaSecretBuilder + UtxoFieldsWithGlobalHDBuilder + UtxoFieldsWithHardwareWalletBuilder @@ -141,7 +145,15 @@ pub trait UtxoFieldsWithIguanaSecretBuilder: UtxoCoinBuilderCommonOps { priv_key: IguanaPrivKey, ) -> UtxoCoinBuildResult { let conf = UtxoConfBuilder::new(self.conf(), self.activation_params(), self.ticker()).build()?; - build_utxo_coin_fields_with_conf_and_secret(self, conf, priv_key).await + let private = Private { + prefix: conf.wif_prefix, + secret: priv_key, + compressed: true, + checksum_type: conf.checksum_type, + }; + let key_pair = KeyPair::from_private(private).map_to_mm(|e| UtxoCoinBuildError::Internal(e.to_string()))?; + let priv_key_policy = PrivKeyPolicy::Iguana(key_pair); + build_utxo_coin_fields_with_conf_and_policy(self, conf, priv_key_policy).await } } @@ -158,27 +170,34 @@ pub trait UtxoFieldsWithGlobalHDBuilder: UtxoCoinBuilderCommonOps { .as_ref() .or_mm_err(|| UtxoConfError::DerivationPathIsNotSet)?; let secret = global_hd_ctx - .derive_secp256k1_secret(derivation_path) + .derive_secp256k1_secret(derivation_path, &self.activation_params().path_to_address) .mm_err(|e| UtxoCoinBuildError::Internal(e.to_string()))?; - build_utxo_coin_fields_with_conf_and_secret(self, conf, secret).await + let private = Private { + prefix: conf.wif_prefix, + secret, + compressed: true, + checksum_type: conf.checksum_type, + }; + let activated_key_pair = + KeyPair::from_private(private).map_to_mm(|e| UtxoCoinBuildError::Internal(e.to_string()))?; + let priv_key_policy = PrivKeyPolicy::HDWallet { + derivation_path: derivation_path.clone(), + activated_key: activated_key_pair, + bip39_secp_priv_key: global_hd_ctx.root_priv_key().clone(), + }; + build_utxo_coin_fields_with_conf_and_policy(self, conf, priv_key_policy).await } } -async fn build_utxo_coin_fields_with_conf_and_secret( +async fn build_utxo_coin_fields_with_conf_and_policy( builder: &Builder, conf: UtxoCoinConf, - secret: Secp256k1Secret, + priv_key_policy: PrivKeyPolicy, ) -> UtxoCoinBuildResult where Builder: UtxoCoinBuilderCommonOps + Sync + ?Sized, { - let private = Private { - prefix: conf.wif_prefix, - secret, - compressed: true, - checksum_type: conf.checksum_type, - }; - let key_pair = KeyPair::from_private(private).map_to_mm(|e| UtxoCoinBuildError::Internal(e.to_string()))?; + let key_pair = priv_key_policy.activated_key_or_err()?; let addr_format = builder.address_format()?; let my_address = Address { prefix: conf.pub_addr_prefix, @@ -191,7 +210,6 @@ where let my_script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); let derivation_method = DerivationMethod::SingleAddress(my_address); - let priv_key_policy = PrivKeyPolicy::KeyPair(key_pair); // Create an abortable system linked to the `MmCtx` so if the context is stopped via `MmArc::stop`, // all spawned futures related to this `UTXO` coin will be aborted as well. diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 053ab674b8..1a4c8e6c69 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -698,9 +698,17 @@ pub fn address_from_str_unchecked(coin: &UtxoCoinFields, address: &str) -> MmRes pub fn my_public_key(coin: &UtxoCoinFields) -> Result<&Public, MmError> { match coin.priv_key_policy { - PrivKeyPolicy::KeyPair(ref key_pair) => Ok(key_pair.public()), - // Hardware Wallets requires BIP39/BIP44 derivation path to extract a public key. - PrivKeyPolicy::Trezor => MmError::err(UnexpectedDerivationMethod::ExpectedSingleAddress), + PrivKeyPolicy::Iguana(ref key_pair) => Ok(key_pair.public()), + PrivKeyPolicy::HDWallet { + activated_key: ref activated_key_pair, + .. + } => Ok(activated_key_pair.public()), + // Hardware Wallets requires BIP32/BIP44 derivation path to extract a public key. + PrivKeyPolicy::Trezor => MmError::err(UnexpectedDerivationMethod::Trezor), + #[cfg(target_arch = "wasm32")] + PrivKeyPolicy::Metamask(_) => MmError::err(UnexpectedDerivationMethod::UnsupportedError( + "`PrivKeyPolicy::Metamask` is not supported in this context".to_string(), + )), } } @@ -2548,7 +2556,7 @@ pub fn sign_message_hash(coin: &UtxoCoinFields, message: &str) -> Option<[u8; 32 pub fn sign_message(coin: &UtxoCoinFields, message: &str) -> SignatureResult { let message_hash = sign_message_hash(coin, message).ok_or(SignatureError::PrefixNotFound)?; - let private_key = coin.priv_key_policy.key_pair_or_err()?.private(); + let private_key = coin.priv_key_policy.activated_key_or_err()?.private(); let signature = private_key.sign_compact(&H256::from(message_hash))?; Ok(base64::encode(&*signature)) } @@ -2689,8 +2697,14 @@ pub fn current_block(coin: &UtxoCoinFields) -> Box Result { match coin.priv_key_policy { - PrivKeyPolicy::KeyPair(ref key_pair) => Ok(key_pair.private().to_string()), + PrivKeyPolicy::Iguana(ref key_pair) => Ok(key_pair.private().to_string()), + PrivKeyPolicy::HDWallet { + activated_key: ref activated_key_pair, + .. + } => Ok(activated_key_pair.private().to_string()), PrivKeyPolicy::Trezor => ERR!("'display_priv_key' doesn't support Hardware Wallets"), + #[cfg(target_arch = "wasm32")] + PrivKeyPolicy::Metamask(_) => ERR!("'display_priv_key' doesn't support Metamask"), } } @@ -2816,6 +2830,11 @@ where } HDAccountAddressId::from(derivation_path) }, + WithdrawFrom::HDWalletAddress(_) => { + return MmError::err(WithdrawError::UnsupportedError( + "`WithdrawFrom::HDWalletAddress` is not supported for `get_withdraw_hd_sender`".to_string(), + )) + }, }; let hd_account = hd_wallet @@ -4525,8 +4544,14 @@ where #[inline] pub fn derive_htlc_key_pair(coin: &UtxoCoinFields, _swap_unique_data: &[u8]) -> KeyPair { match coin.priv_key_policy { - PrivKeyPolicy::KeyPair(k) => k, + PrivKeyPolicy::Iguana(k) => k, + PrivKeyPolicy::HDWallet { + activated_key: activated_key_pair, + .. + } => activated_key_pair, PrivKeyPolicy::Trezor => todo!(), + #[cfg(target_arch = "wasm32")] + PrivKeyPolicy::Metamask(_) => panic!("`PrivKeyPolicy::Metamask` is not supported for UTXO coins"), } } diff --git a/mm2src/coins/utxo/utxo_common_tests.rs b/mm2src/coins/utxo/utxo_common_tests.rs index 45dbbb84bf..246cadcbdb 100644 --- a/mm2src/coins/utxo/utxo_common_tests.rs +++ b/mm2src/coins/utxo/utxo_common_tests.rs @@ -80,7 +80,7 @@ pub(super) fn utxo_coin_fields_for_test( }; let my_script_pubkey = Builder::build_p2pkh(&my_address.hash).to_bytes(); - let priv_key_policy = PrivKeyPolicy::KeyPair(key_pair); + let priv_key_policy = PrivKeyPolicy::Iguana(key_pair); let derivation_method = DerivationMethod::SingleAddress(my_address); let bech32_hrp = if is_segwit_coin { diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index e7e061a176..be56824f6a 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -546,7 +546,7 @@ fn test_search_for_swap_tx_spend_electrum_was_refunded() { let search_input = SearchForSwapTxSpendInput { time_lock: 1591933469, - other_pub: coin.as_ref().priv_key_policy.key_pair_or_err().unwrap().public(), + other_pub: coin.as_ref().priv_key_policy.activated_key_or_err().unwrap().public(), secret_hash: &secret_hash, tx: &payment_tx_bytes, search_from_block: 0, @@ -3360,7 +3360,7 @@ fn test_split_qtum() { let coin = block_on(qtum_coin_with_priv_key(&ctx, "QTUM", &conf, ¶ms, priv_key)).unwrap(); let p2pkh_address = coin.as_ref().derivation_method.unwrap_single_addr(); let script: Script = output_script(p2pkh_address, ScriptType::P2PKH); - let key_pair = coin.as_ref().priv_key_policy.key_pair_or_err().unwrap(); + let key_pair = coin.as_ref().priv_key_policy.activated_key_or_err().unwrap(); let (unspents, _) = block_on(coin.get_mature_unspent_ordered_list(p2pkh_address)).expect("Unspent list is empty"); log!("Mature unspents vec = {:?}", unspents.mature); let outputs = vec![ diff --git a/mm2src/coins/utxo/utxo_withdraw.rs b/mm2src/coins/utxo/utxo_withdraw.rs index de5bad9f32..f51726c6c4 100644 --- a/mm2src/coins/utxo/utxo_withdraw.rs +++ b/mm2src/coins/utxo/utxo_withdraw.rs @@ -3,14 +3,14 @@ use crate::utxo::utxo_common::{big_decimal_from_sat, UtxoTxBuilder}; use crate::utxo::{output_script, sat_from_big_decimal, ActualTxFee, Address, FeePolicy, GetUtxoListOps, PrivKeyPolicy, UtxoAddressFormat, UtxoCoinFields, UtxoCommonOps, UtxoFeeDetails, UtxoTx, UTXO_LOCK}; use crate::{CoinWithDerivationMethod, GetWithdrawSenderAddress, MarketCoinOps, TransactionDetails, WithdrawError, - WithdrawFee, WithdrawRequest, WithdrawResult}; + WithdrawFee, WithdrawFrom, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use chain::TransactionOutput; use common::log::info; use common::now_sec; use crypto::trezor::{TrezorError, TrezorProcessingError}; use crypto::{from_hw_error, CryptoCtx, CryptoCtxError, DerivationPath, HwError, HwProcessingError, HwRpcError}; -use keys::{Public as PublicKey, Type as ScriptType}; +use keys::{AddressHashEnum, KeyPair, Private, Public as PublicKey, Type as ScriptType}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use rpc::v1::types::ToTxHash; @@ -310,11 +310,22 @@ where .or_mm_err(|| WithdrawError::HwError(HwRpcError::NoTrezorDeviceAvailable))?; let sign_policy = match self.coin.as_ref().priv_key_policy { - PrivKeyPolicy::KeyPair(ref key_pair) => SignPolicy::WithKeyPair(key_pair), + PrivKeyPolicy::Iguana(ref key_pair) => SignPolicy::WithKeyPair(key_pair), + // InitUtxoWithdraw works only for hardware wallets so it's ok to use signing with activated keypair here as a placeholder. + PrivKeyPolicy::HDWallet { + activated_key: ref activated_key_pair, + .. + } => SignPolicy::WithKeyPair(activated_key_pair), PrivKeyPolicy::Trezor => { let trezor_session = hw_ctx.trezor().await?; SignPolicy::WithTrezor(trezor_session) }, + #[cfg(target_arch = "wasm32")] + PrivKeyPolicy::Metamask(_) => { + return MmError::err(WithdrawError::UnsupportedError( + "`PrivKeyPolicy::Metamask` is not supported for UTXO coins!".to_string(), + )) + }, }; self.task_handle @@ -365,6 +376,7 @@ impl<'a, Coin> InitUtxoWithdraw<'a, Coin> { pub struct StandardUtxoWithdraw { coin: Coin, req: WithdrawRequest, + key_pair: KeyPair, my_address: Address, my_address_string: String, } @@ -387,10 +399,9 @@ where fn on_finishing(&self) -> Result<(), MmError> { Ok(()) } async fn sign_tx(&self, unsigned_tx: TransactionInputSigner) -> Result> { - let key_pair = self.coin.as_ref().priv_key_policy.key_pair_or_err()?; Ok(with_key_pair::sign_tx( unsigned_tx, - key_pair, + &self.key_pair, self.prev_script(), self.signature_version(), self.coin.as_ref().conf.fork_id, @@ -404,11 +415,52 @@ where { #[allow(clippy::result_large_err)] pub fn new(coin: Coin, req: WithdrawRequest) -> Result> { - let my_address = coin.as_ref().derivation_method.single_addr_or_err()?.clone(); - let my_address_string = coin.my_address()?; + let (key_pair, my_address) = match req.from { + Some(WithdrawFrom::HDWalletAddress(ref path_to_address)) => { + let secret = coin + .as_ref() + .priv_key_policy + .hd_wallet_derived_priv_key_or_err(path_to_address)?; + let private = Private { + prefix: coin.as_ref().conf.wif_prefix, + secret, + compressed: true, + checksum_type: coin.as_ref().conf.checksum_type, + }; + let key_pair = + KeyPair::from_private(private).map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; + let addr_format = coin + .as_ref() + .derivation_method + .single_addr_or_err()? + .clone() + .addr_format; + let my_address = Address { + prefix: coin.as_ref().conf.pub_addr_prefix, + t_addr_prefix: coin.as_ref().conf.pub_t_addr_prefix, + hash: AddressHashEnum::AddressHash(key_pair.public().address_hash()), + checksum_type: coin.as_ref().conf.checksum_type, + hrp: coin.as_ref().conf.bech32_hrp.clone(), + addr_format, + }; + (key_pair, my_address) + }, + Some(WithdrawFrom::AddressId(_)) | Some(WithdrawFrom::DerivationPath { .. }) => { + return MmError::err(WithdrawError::UnsupportedError( + "Only `WithdrawFrom::HDWalletAddress` is supported for `StandardUtxoWithdraw`".to_string(), + )) + }, + None => { + let key_pair = coin.as_ref().priv_key_policy.activated_key_or_err()?; + let my_address = coin.as_ref().derivation_method.single_addr_or_err()?.clone(); + (*key_pair, my_address) + }, + }; + let my_address_string = my_address.display_address().map_to_mm(WithdrawError::InternalError)?; Ok(StandardUtxoWithdraw { coin, req, + key_pair, my_address, my_address_string, }) diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index c911bf4a7e..9d0b042297 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -34,8 +34,8 @@ use common::executor::{AbortableSystem, AbortedError}; use common::sha256_digest; use common::{log, one_thousand_u32}; use crypto::privkey::{key_pair_from_secret, secp_privkey_from_hash}; -use crypto::StandardHDPathToCoin; use crypto::{Bip32DerPathOps, GlobalHDAccountArc}; +use crypto::{StandardHDCoinAddress, StandardHDPathToCoin}; use futures::compat::Future01CompatExt; use futures::lock::Mutex as AsyncMutex; use futures::{FutureExt, TryFutureExt}; @@ -324,7 +324,7 @@ impl ZCoin { fn secp_keypair(&self) -> &KeyPair { self.utxo_arc .priv_key_policy - .key_pair() + .activated_key() .expect("Zcoin doesn't support HW wallets") } @@ -776,6 +776,8 @@ pub struct ZcoinActivationParams { pub scan_blocks_per_iteration: u32, #[serde(default)] pub scan_interval_ms: u64, + #[serde(default)] + pub account: u32, } #[cfg(not(target_arch = "wasm32"))] @@ -867,7 +869,11 @@ impl<'a> UtxoCoinBuilder for ZCoinBuilder<'a> { let z_spending_key = match self.z_spending_key { Some(ref z_spending_key) => z_spending_key.clone(), - None => extended_spending_key_from_protocol_info_and_policy(&self.protocol_info, &self.priv_key_policy)?, + None => extended_spending_key_from_protocol_info_and_policy( + &self.protocol_info, + &self.priv_key_policy, + self.z_coin_params.account, + )?, }; let (_, my_z_addr) = z_spending_key @@ -888,7 +894,7 @@ impl<'a> UtxoCoinBuilder for ZCoinBuilder<'a> { ); let blocks_db = self.blocks_db().await?; - let wallet_db = WalletDbShared::new(&self) + let wallet_db = WalletDbShared::new(&self, &z_spending_key) .await .map_err(|err| ZCoinBuildError::ZcashDBError(err.to_string()))?; @@ -973,6 +979,8 @@ impl<'a> ZCoinBuilder<'a> { enable_params: Default::default(), priv_key_policy: PrivKeyActivationPolicy::ContextPrivKey, check_utxo_maturity: None, + // This is not used for Zcoin so we just provide a default value + path_to_address: StandardHDCoinAddress::default(), }; ZCoinBuilder { ctx, @@ -1901,11 +1909,17 @@ impl InitWithdrawCoin for ZCoin { task_handle: &WithdrawTaskHandle, ) -> Result> { if req.fee.is_some() { - return MmError::err(WithdrawError::InternalError( + return MmError::err(WithdrawError::UnsupportedError( "Setting a custom withdraw fee is not supported for ZCoin yet".to_owned(), )); } + if req.from.is_some() { + return MmError::err(WithdrawError::UnsupportedError( + "Withdraw from a specific address is not supported for ZCoin yet".to_owned(), + )); + } + let to_addr = decode_payment_address(z_mainnet_constants::HRP_SAPLING_PAYMENT_ADDRESS, &req.to) .map_to_mm(|e| WithdrawError::InvalidAddress(format!("{}", e)))? .or_mm_err(|| WithdrawError::InvalidAddress(format!("Address {} decoded to None", req.to)))?; @@ -1985,11 +1999,12 @@ pub fn interpret_memo_string(memo_str: &str) -> MmResult MmResult { match priv_key_policy { PrivKeyBuildPolicy::IguanaPrivKey(iguana) => Ok(ExtendedSpendingKey::master(iguana.as_slice())), PrivKeyBuildPolicy::GlobalHDAccount(global_hd) => { - extended_spending_key_from_global_hd_account(protocol_info, global_hd) + extended_spending_key_from_global_hd_account(protocol_info, global_hd, account) }, PrivKeyBuildPolicy::Trezor => { let priv_key_err = PrivKeyPolicyNotAllowed::HardwareWalletNotSupported; @@ -2003,12 +2018,12 @@ fn extended_spending_key_from_protocol_info_and_policy( fn extended_spending_key_from_global_hd_account( protocol_info: &ZcoinProtocolInfo, global_hd: &GlobalHDAccountArc, + account: u32, ) -> MmResult { let path_to_coin = protocol_info .z_derivation_path .clone() .or_mm_err(|| ZCoinBuildError::ZDerivationPathNotSet)?; - let path_to_account = path_to_coin .to_derivation_path() .into_iter() @@ -2016,7 +2031,7 @@ fn extended_spending_key_from_global_hd_account( .map(|child| Zip32Child::from_index(child.0)) // Push the hardened `account` index, so the derivation path looks like: // `m/purpose'/coin'/account'`. - .chain(iter::once(Zip32Child::Hardened(global_hd.account_id()))); + .chain(iter::once(Zip32Child::Hardened(account))); let mut spending_key = ExtendedSpendingKey::master(global_hd.root_seed_bytes()); for zip32_child in path_to_account { diff --git a/mm2src/coins/z_coin/storage/walletdb/mod.rs b/mm2src/coins/z_coin/storage/walletdb/mod.rs index c7773df4ab..623ff042dc 100644 --- a/mm2src/coins/z_coin/storage/walletdb/mod.rs +++ b/mm2src/coins/z_coin/storage/walletdb/mod.rs @@ -1,8 +1,9 @@ use crate::z_coin::{ZCoinBuilder, ZcoinClientInitError}; use mm2_err_handle::prelude::*; +use zcash_primitives::zip32::ExtendedSpendingKey; cfg_native!( - use crate::z_coin::{extended_spending_key_from_protocol_info_and_policy, ZcoinConsensusParams}; + use crate::z_coin::{ZcoinConsensusParams}; use crate::z_coin::z_rpc::create_wallet_db; use parking_lot::Mutex; @@ -35,22 +36,17 @@ pub struct WalletDbShared { #[cfg(not(target_arch = "wasm32"))] impl<'a> WalletDbShared { - pub async fn new(zcoin_builder: &ZCoinBuilder<'a>) -> MmResult { - let z_spending_key = match zcoin_builder.z_spending_key { - Some(ref z_spending_key) => z_spending_key.clone(), - None => extended_spending_key_from_protocol_info_and_policy( - &zcoin_builder.protocol_info, - &zcoin_builder.priv_key_policy, - ) - .map_err(|err| WalletDbError::ZCoinBuildError(err.to_string()))?, - }; + pub async fn new( + zcoin_builder: &ZCoinBuilder<'a>, + z_spending_key: &ExtendedSpendingKey, + ) -> MmResult { let wallet_db = create_wallet_db( zcoin_builder .db_dir_path .join(format!("{}_wallet.db", zcoin_builder.ticker)), zcoin_builder.protocol_info.consensus_params.clone(), zcoin_builder.protocol_info.check_point_block.clone(), - ExtendedFullViewingKey::from(&z_spending_key), + ExtendedFullViewingKey::from(z_spending_key), ) .await .map_err(|err| MmError::new(WalletDbError::ZcoinClientInitError(err.into_inner())))?; @@ -69,7 +65,10 @@ cfg_wasm32!( pub type WalletDbInnerLocked<'a> = DbLocked<'a, WalletDbInner>; impl<'a> WalletDbShared { - pub async fn new(zcoin_builder: &ZCoinBuilder<'a>) -> MmResult { + pub async fn new( + zcoin_builder: &ZCoinBuilder<'a>, + _z_spending_key: &ExtendedSpendingKey, + ) -> MmResult { Ok(Self { db: ConstructibleDb::new(zcoin_builder.ctx).into_shared(), ticker: zcoin_builder.ticker.to_string(), diff --git a/mm2src/coins/z_coin/z_coin_native_tests.rs b/mm2src/coins/z_coin/z_coin_native_tests.rs index 00df86e612..efe4079ea2 100644 --- a/mm2src/coins/z_coin/z_coin_native_tests.rs +++ b/mm2src/coins/z_coin/z_coin_native_tests.rs @@ -38,7 +38,7 @@ fn zombie_coin_send_and_refund_maker_payment() { .unwrap(); let time_lock = now_sec_u32() - 3600; - let taker_pub = coin.utxo_arc.priv_key_policy.key_pair_or_err().unwrap().public(); + let taker_pub = coin.utxo_arc.priv_key_policy.activated_key_or_err().unwrap().public(); let secret_hash = [0; 20]; let args = SendPaymentArgs { @@ -95,7 +95,7 @@ fn zombie_coin_send_and_spend_maker_payment() { .unwrap(); let lock_time = now_sec_u32() - 1000; - let taker_pub = coin.utxo_arc.priv_key_policy.key_pair_or_err().unwrap().public(); + let taker_pub = coin.utxo_arc.priv_key_policy.activated_key_or_err().unwrap().public(); let secret = [0; 32]; let secret_hash = dhash160(&secret); @@ -289,5 +289,6 @@ fn default_zcoin_activation_params() -> ZcoinActivationParams { zcash_params_path: None, scan_blocks_per_iteration: 0, scan_interval_ms: 0, + account: 0, } } diff --git a/mm2src/coins_activation/src/eth_with_token_activation.rs b/mm2src/coins_activation/src/eth_with_token_activation.rs index abbe91a106..0f7c8d8455 100644 --- a/mm2src/coins_activation/src/eth_with_token_activation.rs +++ b/mm2src/coins_activation/src/eth_with_token_activation.rs @@ -43,9 +43,6 @@ impl From for EnablePlatformCoinWithTokensError { EthActivationV2Error::CouldNotFetchBalance(e) | EthActivationV2Error::UnreachableNodes(e) => { EnablePlatformCoinWithTokensError::Transport(e) }, - EthActivationV2Error::DerivationPathIsNotSet => EnablePlatformCoinWithTokensError::InvalidPayload( - "'derivation_path' field is not found in config".to_string(), - ), EthActivationV2Error::ErrorDeserializingDerivationPath(e) => { EnablePlatformCoinWithTokensError::InvalidPayload(e) }, diff --git a/mm2src/coins_activation/src/tendermint_with_assets_activation.rs b/mm2src/coins_activation/src/tendermint_with_assets_activation.rs index fa0b5f9c47..006f7993d3 100644 --- a/mm2src/coins_activation/src/tendermint_with_assets_activation.rs +++ b/mm2src/coins_activation/src/tendermint_with_assets_activation.rs @@ -6,12 +6,13 @@ use crate::prelude::*; use async_trait::async_trait; use coins::my_tx_history_v2::TxHistoryStorage; use coins::tendermint::tendermint_tx_history_v2::tendermint_history_loop; -use coins::tendermint::{TendermintCoin, TendermintCommons, TendermintConf, TendermintInitError, - TendermintInitErrorKind, TendermintProtocolInfo, TendermintToken, +use coins::tendermint::{tendermint_priv_key_policy, TendermintCoin, TendermintCommons, TendermintConf, + TendermintInitError, TendermintInitErrorKind, TendermintProtocolInfo, TendermintToken, TendermintTokenActivationParams, TendermintTokenInitError, TendermintTokenProtocolInfo}; use coins::{CoinBalance, CoinProtocol, MarketCoinOps, MmCoin, MmCoinEnum, PrivKeyBuildPolicy}; use common::executor::{AbortSettings, SpawnAbortable}; use common::{true_f, Future01CompatExt}; +use crypto::StandardHDCoinAddress; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use mm2_number::BigDecimal; @@ -37,6 +38,9 @@ pub struct TendermintActivationParams { tx_history: bool, #[serde(default = "true_f")] pub get_balances: bool, + /// /account'/change/address_index`. + #[serde(default)] + pub path_to_address: StandardHDCoinAddress, } impl TxHistory for TendermintActivationParams { @@ -168,10 +172,18 @@ impl PlatformWithTokensActivationOps for TendermintCoin { ) -> Result> { let conf = TendermintConf::try_from_json(&ticker, &coin_conf)?; - let priv_key_policy = PrivKeyBuildPolicy::detect_priv_key_policy(&ctx).mm_err(|e| TendermintInitError { - ticker: ticker.clone(), - kind: TendermintInitErrorKind::Internal(e.to_string()), - })?; + let priv_key_build_policy = + PrivKeyBuildPolicy::detect_priv_key_policy(&ctx).mm_err(|e| TendermintInitError { + ticker: ticker.clone(), + kind: TendermintInitErrorKind::Internal(e.to_string()), + })?; + + let priv_key_policy = tendermint_priv_key_policy( + &conf, + &ticker, + priv_key_build_policy, + activation_request.path_to_address, + )?; TendermintCoin::init( &ctx, diff --git a/mm2src/crypto/src/crypto_ctx.rs b/mm2src/crypto/src/crypto_ctx.rs index 4e2391e362..4f91448af4 100644 --- a/mm2src/crypto/src/crypto_ctx.rs +++ b/mm2src/crypto/src/crypto_ctx.rs @@ -29,11 +29,6 @@ pub enum CryptoInitError { EmptyPassphrase, #[display(fmt = "Invalid passphrase: '{}'", _0)] InvalidPassphrase(PrivKeyError), - #[display(fmt = "Invalid 'hd_account_id' = {}: {}", hd_account_id, error)] - InvalidHdAccount { - hd_account_id: u64, - error: String, - }, Internal(String), } @@ -223,12 +218,8 @@ impl CryptoCtx { Self::init_crypto_ctx_with_policy_builder(ctx, passphrase, KeyPairPolicyBuilder::Iguana) } - pub fn init_with_global_hd_account( - ctx: MmArc, - passphrase: &str, - hd_account_id: u64, - ) -> CryptoInitResult> { - let builder = KeyPairPolicyBuilder::GlobalHDAccount { hd_account_id }; + pub fn init_with_global_hd_account(ctx: MmArc, passphrase: &str) -> CryptoInitResult> { + let builder = KeyPairPolicyBuilder::GlobalHDAccount; Self::init_crypto_ctx_with_policy_builder(ctx, passphrase, builder) } @@ -338,7 +329,7 @@ impl CryptoCtx { enum KeyPairPolicyBuilder { Iguana, - GlobalHDAccount { hd_account_id: u64 }, + GlobalHDAccount, } impl KeyPairPolicyBuilder { @@ -349,8 +340,8 @@ impl KeyPairPolicyBuilder { let secp256k1_key_pair = key_pair_from_seed(passphrase)?; Ok((secp256k1_key_pair, KeyPairPolicy::Iguana)) }, - KeyPairPolicyBuilder::GlobalHDAccount { hd_account_id } => { - let (mm2_internal_key_pair, global_hd_ctx) = GlobalHDAccountCtx::new(passphrase, hd_account_id)?; + KeyPairPolicyBuilder::GlobalHDAccount => { + let (mm2_internal_key_pair, global_hd_ctx) = GlobalHDAccountCtx::new(passphrase)?; let key_pair_policy = KeyPairPolicy::GlobalHDAccount(global_hd_ctx.into_arc()); Ok((mm2_internal_key_pair, key_pair_policy)) }, diff --git a/mm2src/crypto/src/global_hd_ctx.rs b/mm2src/crypto/src/global_hd_ctx.rs index 13b2736f41..79326e9889 100644 --- a/mm2src/crypto/src/global_hd_ctx.rs +++ b/mm2src/crypto/src/global_hd_ctx.rs @@ -1,11 +1,11 @@ use crate::privkey::{bip39_seed_from_passphrase, key_pair_from_secret, PrivKeyError}; +use crate::standard_hd_path::StandardHDCoinAddress; use crate::{mm2_internal_der_path, Bip32DerPathOps, Bip32Error, CryptoInitError, CryptoInitResult, StandardHDPathToCoin}; use bip32::{ChildNumber, ExtendedPrivateKey}; +use common::drop_mutability; use keys::{KeyPair, Secret as Secp256k1Secret}; use mm2_err_handle::prelude::*; -use std::convert::TryInto; -use std::num::TryFromIntError; use std::ops::Deref; use std::sync::Arc; @@ -26,31 +26,16 @@ impl Deref for GlobalHDAccountArc { pub struct GlobalHDAccountCtx { bip39_seed: bip39::Seed, bip39_secp_priv_key: ExtendedPrivateKey, - /// This account is set globally for every activated coin. - hd_account: ChildNumber, } impl GlobalHDAccountCtx { - pub fn new(passphrase: &str, hd_account_id: u64) -> CryptoInitResult<(Mm2InternalKeyPair, GlobalHDAccountCtx)> { + pub fn new(passphrase: &str) -> CryptoInitResult<(Mm2InternalKeyPair, GlobalHDAccountCtx)> { let bip39_seed = bip39_seed_from_passphrase(passphrase)?; let bip39_secp_priv_key: ExtendedPrivateKey = ExtendedPrivateKey::new(bip39_seed.as_bytes()) .map_to_mm(|e| PrivKeyError::InvalidPrivKey(e.to_string()))?; - let hd_account_id = - hd_account_id - .try_into() - .map_to_mm(|e: TryFromIntError| CryptoInitError::InvalidHdAccount { - hd_account_id, - error: e.to_string(), - })?; - let hd_account = - ChildNumber::new(hd_account_id, NON_HARDENED).map_to_mm(|e| CryptoInitError::InvalidHdAccount { - hd_account_id: hd_account_id as u64, - error: e.to_string(), - })?; - - let derivation_path = mm2_internal_der_path(Some(hd_account)); + let derivation_path = mm2_internal_der_path(); let mut internal_priv_key = bip39_secp_priv_key.clone(); for child in derivation_path { @@ -64,7 +49,6 @@ impl GlobalHDAccountCtx { let global_hd_ctx = GlobalHDAccountCtx { bip39_seed, bip39_secp_priv_key, - hd_account, }; Ok((mm2_internal_key_pair, global_hd_ctx)) } @@ -72,15 +56,15 @@ impl GlobalHDAccountCtx { #[inline] pub fn into_arc(self) -> GlobalHDAccountArc { GlobalHDAccountArc(Arc::new(self)) } - /// Returns an identifier of the selected HD account. - pub fn account_id(&self) -> u32 { self.hd_account.index() } - /// Returns the root BIP39 seed. pub fn root_seed(&self) -> &bip39::Seed { &self.bip39_seed } /// Returns the root BIP39 seed as bytes. pub fn root_seed_bytes(&self) -> &[u8] { self.bip39_seed.as_bytes() } + /// Returns the root BIP39 private key. + pub fn root_priv_key(&self) -> &ExtendedPrivateKey { &self.bip39_secp_priv_key } + /// Derives a `secp256k1::SecretKey` from [`HDAccountCtx::bip39_secp_priv_key`] /// at the given `m/purpose'/coin_type'/account_id'/chain/address_id` derivation path, /// where: @@ -92,21 +76,28 @@ impl GlobalHDAccountCtx { pub fn derive_secp256k1_secret( &self, derivation_path: &StandardHDPathToCoin, + path_to_address: &StandardHDCoinAddress, ) -> MmResult { - const ACCOUNT_ID: u32 = 0; - const CHAIN_ID: u32 = 0; - - let mut account_der_path = derivation_path.to_derivation_path(); - account_der_path.push(ChildNumber::new(ACCOUNT_ID, HARDENED).unwrap()); - account_der_path.push(ChildNumber::new(CHAIN_ID, NON_HARDENED).unwrap()); - account_der_path.push(self.hd_account); - - let mut priv_key = self.bip39_secp_priv_key.clone(); - for child in account_der_path { - priv_key = priv_key.derive_child(child)?; - } + derive_secp256k1_secret(self.bip39_secp_priv_key.clone(), derivation_path, path_to_address) + } +} - let secret = *priv_key.private_key().as_ref(); - Ok(Secp256k1Secret::from(secret)) +pub fn derive_secp256k1_secret( + bip39_secp_priv_key: ExtendedPrivateKey, + derivation_path: &StandardHDPathToCoin, + path_to_address: &StandardHDCoinAddress, +) -> MmResult { + let mut account_der_path = derivation_path.to_derivation_path(); + account_der_path.push(ChildNumber::new(path_to_address.account, HARDENED).unwrap()); + account_der_path.push(ChildNumber::new(path_to_address.is_change as u32, NON_HARDENED).unwrap()); + account_der_path.push(ChildNumber::new(path_to_address.address_index, NON_HARDENED).unwrap()); + + let mut priv_key = bip39_secp_priv_key; + for child in account_der_path { + priv_key = priv_key.derive_child(child)?; } + drop_mutability!(priv_key); + + let secret = *priv_key.private_key().as_ref(); + Ok(Secp256k1Secret::from(secret)) } diff --git a/mm2src/crypto/src/hw_ctx.rs b/mm2src/crypto/src/hw_ctx.rs index 50a8c6a6a9..f5dc984407 100644 --- a/mm2src/crypto/src/hw_ctx.rs +++ b/mm2src/crypto/src/hw_ctx.rs @@ -2,7 +2,6 @@ use crate::hw_client::{HwClient, HwConnectionStatus, HwDeviceInfo, HwProcessingE use crate::hw_error::HwError; use crate::trezor::TrezorSession; use crate::{mm2_internal_der_path, HwWalletType}; -use bip32::ChildNumber; use bitcrypto::dhash160; use common::log::warn; use hw_common::primitives::{EcdsaCurve, Secp256k1ExtendedPublicKey}; @@ -119,9 +118,7 @@ impl HardwareWalletCtx { where Processor: TrezorRequestProcessor + Sync, { - const ADDRESS_INDEX: Option = None; - - let path = mm2_internal_der_path(ADDRESS_INDEX); + let path = mm2_internal_der_path(); let mm2_internal_xpub = trezor .get_public_key( path, diff --git a/mm2src/crypto/src/lib.rs b/mm2src/crypto/src/lib.rs index 65be730906..da9ff99a29 100644 --- a/mm2src/crypto/src/lib.rs +++ b/mm2src/crypto/src/lib.rs @@ -18,7 +18,7 @@ mod xpub; pub use bip32_child::{Bip32Child, Bip32DerPathError, Bip32DerPathOps, Bip44Tail}; pub use crypto_ctx::{CryptoCtx, CryptoCtxError, CryptoInitError, CryptoInitResult, HwCtxInitError, KeyPairPolicy}; -pub use global_hd_ctx::GlobalHDAccountArc; +pub use global_hd_ctx::{derive_secp256k1_secret, GlobalHDAccountArc}; pub use hw_client::{HwClient, HwConnectionStatus, HwDeviceInfo, HwProcessingError, HwPubkey, HwWalletType, TrezorConnectProcessor}; pub use hw_common::primitives::{Bip32Error, ChildNumber, DerivationPath, EcdsaCurve, ExtendedPublicKey, @@ -26,8 +26,8 @@ pub use hw_common::primitives::{Bip32Error, ChildNumber, DerivationPath, EcdsaCu pub use hw_ctx::{HardwareWalletArc, HardwareWalletCtx}; pub use hw_error::{from_hw_error, HwError, HwResult, HwRpcError, WithHwRpcError}; pub use keys::Secret as Secp256k1Secret; -pub use standard_hd_path::{Bip44Chain, StandardHDPath, StandardHDPathError, StandardHDPathToAccount, - StandardHDPathToCoin, UnknownChainError}; +pub use standard_hd_path::{Bip44Chain, StandardHDCoinAddress, StandardHDPath, StandardHDPathError, + StandardHDPathToAccount, StandardHDPathToCoin, UnknownChainError}; pub use trezor; pub use xpub::{XPubConverter, XpubError}; @@ -48,11 +48,9 @@ use std::str::FromStr; /// * `account = (2 ^ 31 - 1) = 2147483647` - latest available account index. /// This number is chosen so that it does not cross with real accounts; /// * `change = 0` - nothing special. -/// * `address_index` - is ether specified by the config or default `0`. -pub(crate) fn mm2_internal_der_path(address_index: Option) -> DerivationPath { - let mut der_path = DerivationPath::from_str("m/44'/141'/2147483647/0").expect("valid derivation path"); - der_path.push(address_index.unwrap_or_default()); - der_path +/// * `address_index = 0`. +pub(crate) fn mm2_internal_der_path() -> DerivationPath { + DerivationPath::from_str("m/44'/141'/2147483647/0/0").expect("valid derivation path") } #[derive(Clone, Debug, PartialEq)] diff --git a/mm2src/crypto/src/standard_hd_path.rs b/mm2src/crypto/src/standard_hd_path.rs index 4d13456b1a..f7d29f2e8b 100644 --- a/mm2src/crypto/src/standard_hd_path.rs +++ b/mm2src/crypto/src/standard_hd_path.rs @@ -122,6 +122,21 @@ impl From for Bip32DerPathError { } } +/// A struct that represents a standard HD path from account to address. +/// +/// This is used in coins activation to specify the default address that will be used for swaps. +/// +/// # Attributes +/// * `account`: The account number of the address. +/// * `is_change`: A flag that indicates whether the address is a change address or not. +/// * `address_index`: The index of the address within the account. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct StandardHDCoinAddress { + pub account: u32, + pub is_change: bool, + pub address_index: u32, +} + #[derive(Clone, Copy, Debug, Eq, PartialEq, Primitive)] pub enum StandardHDIndex { Purpose = 0, diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index d11a47a2ca..8401f2a0fd 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -208,10 +208,6 @@ impl From for MmInitError { }, CryptoInitError::EmptyPassphrase => MmInitError::EmptyPassphrase, CryptoInitError::InvalidPassphrase(pass) => MmInitError::InvalidPassphrase(pass.to_string()), - CryptoInitError::InvalidHdAccount { error, .. } => MmInitError::FieldWrongValueInConfig { - field: "hd_account".to_string(), - error, - }, CryptoInitError::Internal(internal) => MmInitError::Internal(internal), } } @@ -430,9 +426,10 @@ pub async fn lp_init(ctx: MmArc, version: String, datetime: String) -> MmInitRes error: e.to_string(), })?; - match ctx.conf["hd_account_id"].as_u64() { - Some(hd_account_id) => CryptoCtx::init_with_global_hd_account(ctx.clone(), &passphrase, hd_account_id)?, - None => CryptoCtx::init_with_iguana_passphrase(ctx.clone(), &passphrase)?, + // This defaults to false to maintain backward compatibility. + match ctx.conf["enable_hd"].as_bool().unwrap_or(false) { + true => CryptoCtx::init_with_global_hd_account(ctx.clone(), &passphrase)?, + false => CryptoCtx::init_with_iguana_passphrase(ctx.clone(), &passphrase)?, }; } diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index f2d6b91e4b..1507d97771 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -1425,6 +1425,7 @@ mod lp_swap_tests { use coins::MarketCoinOps; use coins::PrivKeyActivationPolicy; use common::{block_on, new_uuid}; + use crypto::StandardHDCoinAddress; use mm2_core::mm_ctx::MmCtxBuilder; use mm2_test_helpers::for_tests::{morty_conf, rick_conf, MORTY_ELECTRUM_ADDRS, RICK_ELECTRUM_ADDRS}; @@ -1802,6 +1803,7 @@ mod lp_swap_tests { enable_params: Default::default(), priv_key_policy: PrivKeyActivationPolicy::ContextPrivKey, check_utxo_maturity: None, + path_to_address: StandardHDCoinAddress::default(), } } diff --git a/mm2src/mm2_main/src/wasm_tests.rs b/mm2src/mm2_main/src/wasm_tests.rs index 6a29a497c2..680ecb3667 100644 --- a/mm2src/mm2_main/src/wasm_tests.rs +++ b/mm2src/mm2_main/src/wasm_tests.rs @@ -1,6 +1,7 @@ use crate::mm2::lp_init; use common::executor::{spawn, Timer}; use common::log::wasm_log::register_wasm_log; +use crypto::StandardHDCoinAddress; use mm2_core::mm_ctx::MmArc; use mm2_rpc::data::legacy::OrderbookResponse; use mm2_test_helpers::electrums::{morty_electrums, rick_electrums}; @@ -46,17 +47,17 @@ async fn test_mm2_stops_impl( let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); // Enable coins on Bob side. Print the replies in case we need the address. - let rc = enable_electrum_json(&mm_bob, RICK, true, rick_electrums()).await; + let rc = enable_electrum_json(&mm_bob, RICK, true, rick_electrums(), None).await; log!("enable RICK (bob): {:?}", rc); - let rc = enable_electrum_json(&mm_bob, MORTY, true, morty_electrums()).await; + let rc = enable_electrum_json(&mm_bob, MORTY, true, morty_electrums(), None).await; log!("enable MORTY (bob): {:?}", rc); // Enable coins on Alice side. Print the replies in case we need the address. - let rc = enable_electrum_json(&mm_alice, RICK, true, rick_electrums()).await; + let rc = enable_electrum_json(&mm_alice, RICK, true, rick_electrums(), None).await; log!("enable RICK (bob): {:?}", rc); - let rc = enable_electrum_json(&mm_alice, MORTY, true, morty_electrums()).await; + let rc = enable_electrum_json(&mm_alice, MORTY, true, morty_electrums(), None).await; log!("enable MORTY (bob): {:?}", rc); start_swaps(&mut mm_bob, &mut mm_alice, pairs, maker_price, taker_price, volume).await; @@ -84,6 +85,8 @@ async fn test_qrc20_tx_history() { test_qrc20_history_impl(Some(wasm_start)).awa async fn trade_base_rel_electrum( bob_priv_key_policy: Mm2InitPrivKeyPolicy, alice_priv_key_policy: Mm2InitPrivKeyPolicy, + bob_path_to_address: Option, + alice_path_to_address: Option, pairs: &[(&'static str, &'static str)], maker_price: f64, taker_price: f64, @@ -91,7 +94,7 @@ async fn trade_base_rel_electrum( ) { let coins = json!([rick_conf(), morty_conf(),]); - let bob_conf = Mm2TestConfForSwap::bob_conf_with_policy(bob_priv_key_policy, &coins); + let bob_conf = Mm2TestConfForSwap::bob_conf_with_policy(&bob_priv_key_policy, &coins); let mut mm_bob = MarketMakerIt::start_async(bob_conf.conf, bob_conf.rpc_password, Some(wasm_start)) .await .unwrap(); @@ -99,7 +102,7 @@ async fn trade_base_rel_electrum( let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); Timer::sleep(1.).await; - let alice_conf = Mm2TestConfForSwap::alice_conf_with_policy(alice_priv_key_policy, &coins, &mm_bob.my_seed_addr()); + let alice_conf = Mm2TestConfForSwap::alice_conf_with_policy(&alice_priv_key_policy, &coins, &mm_bob.my_seed_addr()); let mut mm_alice = MarketMakerIt::start_async(alice_conf.conf, alice_conf.rpc_password, Some(wasm_start)) .await .unwrap(); @@ -107,17 +110,17 @@ async fn trade_base_rel_electrum( let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); // Enable coins on Bob side. Print the replies in case we need the address. - let rc = enable_electrum_json(&mm_bob, RICK, true, rick_electrums()).await; + let rc = enable_electrum_json(&mm_bob, RICK, true, rick_electrums(), bob_path_to_address.clone()).await; log!("enable RICK (bob): {:?}", rc); - let rc = enable_electrum_json(&mm_bob, MORTY, true, morty_electrums()).await; + let rc = enable_electrum_json(&mm_bob, MORTY, true, morty_electrums(), bob_path_to_address).await; log!("enable MORTY (bob): {:?}", rc); // Enable coins on Alice side. Print the replies in case we need the address. - let rc = enable_electrum_json(&mm_alice, RICK, true, rick_electrums()).await; + let rc = enable_electrum_json(&mm_alice, RICK, true, rick_electrums(), alice_path_to_address.clone()).await; log!("enable RICK (bob): {:?}", rc); - let rc = enable_electrum_json(&mm_alice, MORTY, true, morty_electrums()).await; + let rc = enable_electrum_json(&mm_alice, MORTY, true, morty_electrums(), alice_path_to_address).await; log!("enable MORTY (bob): {:?}", rc); let uuids = start_swaps(&mut mm_bob, &mut mm_alice, pairs, maker_price, taker_price, volume).await; @@ -160,7 +163,22 @@ async fn trade_base_rel_electrum( #[wasm_bindgen_test] async fn trade_test_rick_and_morty() { let bob_policy = Mm2InitPrivKeyPolicy::Iguana; - let alice_policy = Mm2InitPrivKeyPolicy::GlobalHDAccount(0); + let alice_policy = Mm2InitPrivKeyPolicy::GlobalHDAccount; + let alice_path_to_address = StandardHDCoinAddress { + account: 0, + is_change: false, + address_index: 0, + }; let pairs: &[_] = &[("RICK", "MORTY")]; - trade_base_rel_electrum(bob_policy, alice_policy, pairs, 1., 1., 0.0001).await; + trade_base_rel_electrum( + bob_policy, + alice_policy, + None, + Some(alice_path_to_address), + pairs, + 1., + 1., + 0.0001, + ) + .await; } diff --git a/mm2src/mm2_main/tests/docker_tests/docker_ordermatch_tests.rs b/mm2src/mm2_main/tests/docker_tests/docker_ordermatch_tests.rs index 10f90abdb8..2a08f18efc 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_ordermatch_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_ordermatch_tests.rs @@ -217,10 +217,10 @@ fn test_ordermatch_custom_orderbook_ticker_both_on_maker() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN-Custom", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1-Custom", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN-Custom", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1-Custom", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, "method": "setprice", @@ -352,10 +352,10 @@ fn test_ordermatch_custom_orderbook_ticker_both_on_taker() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN-Custom", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1-Custom", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN-Custom", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1-Custom", &[], None))); let rc = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, "method": "setprice", @@ -485,10 +485,10 @@ fn test_ordermatch_custom_orderbook_ticker_mixed_case_one() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN-Custom", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1-Custom", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN-Custom", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1-Custom", &[], None))); let rc = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, "method": "setprice", @@ -626,10 +626,10 @@ fn test_ordermatch_custom_orderbook_ticker_mixed_case_two() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1-Custom", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN-Custom", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1-Custom", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN-Custom", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, "method": "setprice", @@ -758,8 +758,8 @@ fn test_zombie_order_after_balance_reduce_and_mm_restart() { let mm_maker = MarketMakerIt::start(conf.clone(), "pass".to_string(), None).unwrap(); let (_dump_log, _dump_dashboard) = mm_dump(&mm_maker.log_path); - log!("{:?}", block_on(enable_native(&mm_maker, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_maker, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_maker, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_maker, "MYCOIN1", &[], None))); let rc = block_on(mm_maker.rpc(&json! ({ "userpass": mm_maker.userpass, @@ -858,8 +858,8 @@ fn test_zombie_order_after_balance_reduce_and_mm_restart() { ); // activate coins to kickstart our order - log!("{:?}", block_on(enable_native(&mm_maker_dup, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_maker_dup, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_maker_dup, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_maker_dup, "MYCOIN1", &[], None))); thread::sleep(Duration::from_secs(5)); diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs index 2079a76141..39dbcbe6e6 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs @@ -840,19 +840,19 @@ pub fn trade_base_rel((base, rel): (&str, &str)) { log!("{:?}", block_on(enable_qrc20_native(&mm_bob, "QICK"))); log!("{:?}", block_on(enable_qrc20_native(&mm_bob, "QORTY"))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "QTUM", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "QTUM", &[], None))); log!("{:?}", block_on(enable_native_bch(&mm_bob, "FORSLP", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "ADEXSLP", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "ADEXSLP", &[], None))); log!("{:?}", block_on(enable_qrc20_native(&mm_alice, "QICK"))); log!("{:?}", block_on(enable_qrc20_native(&mm_alice, "QORTY"))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "QTUM", &[]))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "QTUM", &[], None))); log!("{:?}", block_on(enable_native_bch(&mm_alice, "FORSLP", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "ADEXSLP", &[]))); + log!("{:?}", block_on(enable_native(&mm_alice, "ADEXSLP", &[], None))); let rc = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, "method": "setprice", diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index 4df7bf911a..922bff0623 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -395,8 +395,8 @@ fn order_should_be_cancelled_when_entire_balance_is_withdrawn() { ) .unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "setprice", @@ -528,10 +528,10 @@ fn order_should_be_updated_when_balance_is_decreased_alice_subscribes_after_upda .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, @@ -661,10 +661,10 @@ fn order_should_be_updated_when_balance_is_decreased_alice_subscribes_before_upd .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, @@ -808,10 +808,10 @@ fn test_order_should_be_updated_when_matched_partially() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, @@ -913,10 +913,10 @@ fn test_match_and_trade_setprice_max() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "setprice", @@ -1016,10 +1016,10 @@ fn test_max_taker_vol_swap() { let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); block_on(mm_alice.wait_for_log(22., |log| log.contains(">>>>>>>>> DEX stats "))).unwrap(); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); let price = MmNumber::from((100, 1620)); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, @@ -1132,10 +1132,10 @@ fn test_buy_when_coins_locked_by_other_swap() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "setprice", @@ -1225,10 +1225,10 @@ fn test_sell_when_coins_locked_by_other_swap() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "setprice", @@ -1301,8 +1301,8 @@ fn test_buy_max() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "buy", @@ -1365,8 +1365,8 @@ fn test_maker_trade_preimage() { .unwrap(); let (_dump_log, _dump_dashboard) = mm_dump(&mm.log_path); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[]))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, @@ -1502,8 +1502,8 @@ fn test_taker_trade_preimage() { .unwrap(); let (_dump_log, _dump_dashboard) = mm_dump(&mm.log_path); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[]))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); // `max` field is not supported for `buy/sell` swap methods let rc = block_on(mm.rpc(&json!({ @@ -1643,8 +1643,8 @@ fn test_trade_preimage_not_sufficient_balance() { .unwrap(); let (_dump_log, _dump_dashboard) = mm_dump(&mm.log_path); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[]))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); fill_balance_functor(MmNumber::from("0.000015").to_decimal()); // Try sell the max amount with the zero balance. @@ -1762,8 +1762,8 @@ fn test_trade_preimage_additional_validation() { .unwrap(); let (_dump_log, _dump_dashboard) = mm_dump(&mm.log_path); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[]))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); // Price is too low let rc = block_on(mm.rpc(&json!({ @@ -1902,8 +1902,8 @@ fn test_trade_preimage_legacy() { .unwrap(); let (_dump_log, _dump_dashboard) = mm_dump(&mm.log_path); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[]))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, @@ -1972,8 +1972,8 @@ fn test_get_max_taker_vol() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "max_taker_vol", @@ -2024,8 +2024,8 @@ fn test_get_max_taker_vol_dex_fee_threshold() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "max_taker_vol", @@ -2085,8 +2085,8 @@ fn test_get_max_taker_vol_dust_threshold() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm.log_path); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[]))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, @@ -2136,13 +2136,19 @@ fn test_get_max_taker_vol_with_kmd() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - let electrum = block_on(enable_electrum(&mm_alice, "KMD", false, &[ - "electrum1.cipig.net:10001", - "electrum2.cipig.net:10001", - "electrum3.cipig.net:10001", - ])); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + let electrum = block_on(enable_electrum( + &mm_alice, + "KMD", + false, + &[ + "electrum1.cipig.net:10001", + "electrum2.cipig.net:10001", + "electrum3.cipig.net:10001", + ], + None, + )); log!("{:?}", electrum); let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, @@ -2182,8 +2188,8 @@ fn test_get_max_maker_vol() { let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); let (_dump_log, _dump_dashboard) = mm_dump(&mm.log_path); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN1", &[], None))); // 1 - tx_fee let expected_volume = MmNumber::from("0.99999"); @@ -2208,7 +2214,7 @@ fn test_get_max_maker_vol_error() { let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); let (_dump_log, _dump_dashboard) = mm_dump(&mm.log_path); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[]))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); let actual_error = block_on(max_maker_vol(&mm, "MYCOIN")).unwrap_err::(); let expected_error = max_maker_vol_error::NotSufficientBalance { @@ -2242,8 +2248,8 @@ fn test_set_price_max() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "setprice", @@ -2313,10 +2319,10 @@ fn swaps_should_stop_on_stop_rpc() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "setprice", @@ -2384,8 +2390,8 @@ fn test_maker_order_should_kick_start_and_appear_in_orderbook_on_restart() { let mm_bob = MarketMakerIt::start(bob_conf.clone(), "pass".to_string(), None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "setprice", @@ -2404,8 +2410,8 @@ fn test_maker_order_should_kick_start_and_appear_in_orderbook_on_restart() { let mm_bob_dup = MarketMakerIt::start(bob_conf, "pass".to_string(), None).unwrap(); let (_bob_dup_dump_log, _bob_dup_dump_dashboard) = mm_dump(&mm_bob_dup.log_path); - log!("{:?}", block_on(enable_native(&mm_bob_dup, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob_dup, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob_dup, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob_dup, "MYCOIN1", &[], None))); thread::sleep(Duration::from_secs(2)); @@ -2441,8 +2447,8 @@ fn test_maker_order_should_not_kick_start_and_appear_in_orderbook_if_balance_is_ let mm_bob = MarketMakerIt::start(bob_conf.clone(), "pass".to_string(), None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "setprice", @@ -2480,8 +2486,8 @@ fn test_maker_order_should_not_kick_start_and_appear_in_orderbook_if_balance_is_ let mm_bob_dup = MarketMakerIt::start(bob_conf, "pass".to_string(), None).unwrap(); let (_bob_dup_dump_log, _bob_dup_dump_dashboard) = mm_dump(&mm_bob_dup.log_path); - log!("{:?}", block_on(enable_native(&mm_bob_dup, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob_dup, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob_dup, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob_dup, "MYCOIN1", &[], None))); thread::sleep(Duration::from_secs(2)); @@ -2567,10 +2573,10 @@ fn test_maker_order_kick_start_should_trigger_subscription_and_match() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, @@ -2590,8 +2596,8 @@ fn test_maker_order_kick_start_should_trigger_subscription_and_match() { let mut mm_bob_dup = MarketMakerIt::start(bob_conf, "pass".to_string(), None).unwrap(); let (_bob_dup_dump_log, _bob_dup_dump_dashboard) = mm_dump(&mm_bob_dup.log_path); - log!("{:?}", block_on(enable_native(&mm_bob_dup, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob_dup, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob_dup, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob_dup, "MYCOIN1", &[], None))); log!("Give restarted Bob 2 seconds to kickstart the order"); thread::sleep(Duration::from_secs(2)); @@ -2664,12 +2670,12 @@ fn test_orders_should_match_on_both_nodes_with_same_priv() { .unwrap(); let (_alice_2_dump_log, _alice_2_dump_dashboard) = mm_dump(&mm_alice_2.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice_1, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice_1, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice_2, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice_2, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice_1, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice_1, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice_2, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice_2, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, @@ -2751,10 +2757,10 @@ fn test_maker_and_taker_order_created_with_same_priv_should_not_match() { .unwrap(); let (_alice_1_dump_log, _alice_1_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, @@ -2822,10 +2828,10 @@ fn test_taker_order_converted_to_maker_should_cancel_properly_when_matched() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "sell", @@ -3029,7 +3035,7 @@ fn test_withdraw_not_sufficient_balance() { ) .unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm.log_path); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[]))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); // balance = 0, but amount = 1 let amount = BigDecimal::from(1); @@ -3148,12 +3154,12 @@ fn test_taker_should_match_with_best_price_buy() { .unwrap(); let (_eve_dump_log, _eve_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_eve, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_eve, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_eve, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_eve, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, @@ -3281,12 +3287,12 @@ fn test_taker_should_match_with_best_price_sell() { .unwrap(); let (_eve_dump_log, _eve_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_eve, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_eve, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_eve, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_eve, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, @@ -3403,10 +3409,10 @@ fn test_match_utxo_with_eth_taker_sell() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - block_on(enable_native(&mm_bob, "ETH", ETH_DEV_NODES)); - block_on(enable_native(&mm_alice, "ETH", ETH_DEV_NODES)); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + block_on(enable_native(&mm_bob, "ETH", ETH_DEV_NODES, None)); + block_on(enable_native(&mm_alice, "ETH", ETH_DEV_NODES, None)); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, @@ -3479,11 +3485,11 @@ fn test_match_utxo_with_eth_taker_buy() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - block_on(enable_native(&mm_bob, "ETH", ETH_DEV_NODES)); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + block_on(enable_native(&mm_bob, "ETH", ETH_DEV_NODES, None)); - block_on(enable_native(&mm_alice, "ETH", ETH_DEV_NODES)); + block_on(enable_native(&mm_alice, "ETH", ETH_DEV_NODES, None)); let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, @@ -3529,10 +3535,10 @@ fn test_locked_amount() { let mut mm_alice = MarketMakerIt::start(alice_conf.conf, alice_conf.rpc_password, None).unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); block_on(start_swaps( &mut mm_bob, diff --git a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs index 8a08865490..dc6f915948 100644 --- a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs @@ -934,7 +934,7 @@ fn test_check_balance_on_order_post_base_coin_locked() { let (_dump_log, _dump_dashboard) = mm_dump(&mm_bob.log_path); log!("Log path: {}", mm_bob.log_path.display()); block_on(mm_bob.wait_for_log(22., |log| log.contains(">>>>>>>>> DEX stats "))).unwrap(); - block_on(enable_native(&mm_bob, "MYCOIN", &[])); + block_on(enable_native(&mm_bob, "MYCOIN", &[], None)); block_on(enable_qrc20_native(&mm_bob, "QICK")); // start alice @@ -957,7 +957,7 @@ fn test_check_balance_on_order_post_base_coin_locked() { let (_dump_log, _dump_dashboard) = mm_dump(&mm_alice.log_path); log!("Log path: {}", mm_alice.log_path.display()); block_on(mm_alice.wait_for_log(22., |log| log.contains(">>>>>>>>> DEX stats "))).unwrap(); - block_on(enable_native(&mm_alice, "MYCOIN", &[])); + block_on(enable_native(&mm_alice, "MYCOIN", &[], None)); block_on(enable_qrc20_native(&mm_alice, "QICK")); let rc = block_on(mm_alice.rpc(&json! ({ @@ -1033,8 +1033,8 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm.log_path); block_on(mm.wait_for_log(22., |log| log.contains(">>>>>>>>> DEX stats "))).unwrap(); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm, "QTUM", &[]))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "QTUM", &[], None))); let qtum_balance = coin.my_spendable_balance().wait().expect("!my_balance"); let qtum_dex_fee_threshold = MmNumber::from("0.000728"); @@ -1219,8 +1219,8 @@ fn test_trade_preimage_not_sufficient_base_coin_balance_for_ticker() { let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm.log_path); block_on(mm.wait_for_log(22., |log| log.contains(">>>>>>>>> DEX stats "))).unwrap(); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm, "QICK", &[]))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "QICK", &[], None))); // txfee > 0, amount = 0.005 => required = txfee + amount > 0.005, // but balance = 0.005 @@ -1278,8 +1278,8 @@ fn test_trade_preimage_dynamic_fee_not_sufficient_balance() { let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm.log_path); block_on(mm.wait_for_log(22., |log| log.contains(">>>>>>>>> DEX stats "))).unwrap(); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm, "QTUM", &[]))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "QTUM", &[], None))); // txfee > 0, amount = 0.5 => required = txfee + amount > 0.5, // but balance = 0.5 @@ -1339,8 +1339,8 @@ fn test_trade_preimage_deduct_fee_from_output_failed() { let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm.log_path); block_on(mm.wait_for_log(22., |log| log.contains(">>>>>>>>> DEX stats "))).unwrap(); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm, "QTUM", &[]))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm, "QTUM", &[], None))); let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, @@ -1398,7 +1398,7 @@ fn test_segwit_native_balance() { let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm.log_path); block_on(mm.wait_for_log(22., |log| log.contains(">>>>>>>>> DEX stats "))).unwrap(); - let enable_res = block_on(enable_native(&mm, "QTUM", &[])); + let enable_res = block_on(enable_native(&mm, "QTUM", &[], None)); let expected_balance: BigDecimal = "0.5".parse().unwrap(); assert_eq!(enable_res.balance, expected_balance); @@ -1444,7 +1444,7 @@ fn test_withdraw_and_send_from_segwit() { let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm.log_path); block_on(mm.wait_for_log(22., |log| log.contains(">>>>>>>>> DEX stats "))).unwrap(); - log!("{:?}", block_on(enable_native(&mm, "QTUM", &[]))); + log!("{:?}", block_on(enable_native(&mm, "QTUM", &[], None))); // Send from Segwit Address to Segwit Address withdraw_and_send(&mm, "QTUM", "qcrt1q6pwxl4na4a363mgmrw8tjyppdcwuyfmat836dd", 0.2); @@ -1492,7 +1492,7 @@ fn test_withdraw_and_send_legacy_to_segwit() { let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm.log_path); block_on(mm.wait_for_log(22., |log| log.contains(">>>>>>>>> DEX stats "))).unwrap(); - log!("{:?}", block_on(enable_native(&mm, "QTUM", &[]))); + log!("{:?}", block_on(enable_native(&mm, "QTUM", &[], None))); // Send from Legacy Address to Segwit Address withdraw_and_send(&mm, "QTUM", "qcrt1q6pwxl4na4a363mgmrw8tjyppdcwuyfmat836dd", 0.2); @@ -1694,7 +1694,7 @@ fn segwit_address_in_the_orderbook() { fill_address(&coin, &segwit_addr, 1000.into(), 30); - log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[]))); + log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); let rc = block_on(mm.rpc(&json! ({ "userpass": mm.userpass, diff --git a/mm2src/mm2_main/tests/docker_tests/slp_tests.rs b/mm2src/mm2_main/tests/docker_tests/slp_tests.rs index b1fe7b6b9f..e5ce537b7b 100644 --- a/mm2src/mm2_main/tests/docker_tests/slp_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/slp_tests.rs @@ -70,7 +70,7 @@ fn test_bch_and_slp_balance() { assert_eq!(expected_spendable, bch_balance.balance); assert_eq!(expected_unspendable, bch_balance.unspendable_balance); - let enable_slp = block_on(enable_native(&mm, "ADEXSLP", &[])); + let enable_slp = block_on(enable_native(&mm, "ADEXSLP", &[], None)); let expected_spendable = BigDecimal::from(1000); assert_eq!(expected_spendable, enable_slp.balance); @@ -132,6 +132,7 @@ fn test_bch_and_slp_balance_enable_bch_with_tokens_v2() { &["ADEXSLP"], UtxoRpcMode::Native, tx_history, + None, )); let enable_bch_with_tokens: RpcV2Response = json::from_value(enable_bch_with_tokens).unwrap(); @@ -210,7 +211,7 @@ fn test_withdraw_bch_max_must_not_spend_slp() { let mm = slp_supplied_node(); block_on(enable_native_bch(&mm, "FORSLP", &[])); - block_on(enable_native(&mm, "ADEXSLP", &[])); + block_on(enable_native(&mm, "ADEXSLP", &[], None)); withdraw_max_and_send_v1(&mm, "FORSLP", &utxo_burn_address().to_string()); thread::sleep(Duration::from_secs(1)); @@ -237,6 +238,7 @@ fn test_disable_platform_coin_with_tokens() { &["ADEXSLP"], UtxoRpcMode::Native, false, + None, )); // Try to disable ADEXSLP token. let res = block_on(disable_coin(&mm, "ADEXSLP", false)); @@ -256,6 +258,7 @@ fn test_disable_platform_coin_with_tokens() { &["ADEXSLP"], UtxoRpcMode::Native, false, + None, )); // Try to force disable platform coin, FORSLP. diff --git a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs index 264bbc4f9b..383ebf0879 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -50,7 +50,7 @@ struct BalanceResult { fn enable_coin(mm_node: &MarketMakerIt, coin: &str) { if coin == "MYCOIN" { - log!("{:?}", block_on(enable_native(mm_node, coin, &[]))); + log!("{:?}", block_on(enable_native(mm_node, coin, &[], None))); } else { enable_eth(mm_node, coin); } diff --git a/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs b/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs index 0886e3e83b..13c35cb0be 100644 --- a/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs @@ -60,10 +60,10 @@ fn test_confirmation_settings_sync_correctly_on_buy( .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, "method": "setprice", @@ -225,10 +225,10 @@ fn test_confirmation_settings_sync_correctly_on_sell( .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, "method": "setprice", diff --git a/mm2src/mm2_main/tests/docker_tests/swaps_file_lock_tests.rs b/mm2src/mm2_main/tests/docker_tests/swaps_file_lock_tests.rs index e69733cbff..8ca965e784 100644 --- a/mm2src/mm2_main/tests/docker_tests/swaps_file_lock_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swaps_file_lock_tests.rs @@ -48,8 +48,8 @@ fn swap_file_lock_prevents_double_swap_start_on_kick_start(swap_json: &str) { }); let mut mm_bob = MarketMakerIt::start(bob_conf, "pass".to_string(), None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); block_on(mm_bob.wait_for_log(22., |log| { log.contains("Kick starting the swap 5acb0e63-8b26-469e-81df-7dd9e4a9ad15") })) @@ -104,10 +104,10 @@ fn test_swaps_should_kick_start_if_process_was_killed() { let mut mm_alice = MarketMakerIt::start(alice_conf.clone(), "pass".to_string(), None).unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); let rc = block_on(mm_bob.rpc(&json! ({ "userpass": mm_bob.userpass, "method": "setprice", @@ -153,8 +153,8 @@ fn test_swaps_should_kick_start_if_process_was_killed() { drop(mm_bob); let mut mm_bob_dup = MarketMakerIt::start(bob_conf, "pass".to_string(), None).unwrap(); let (_bob_dup_dump_log, _bob_dup_dump_dashboard) = mm_dump(&mm_bob_dup.log_path); - log!("{:?}", block_on(enable_native(&mm_bob_dup, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob_dup, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob_dup, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob_dup, "MYCOIN1", &[], None))); block_on(mm_bob_dup.wait_for_log(50., |log| log.contains(&format!("Swap {} kick started.", uuid)))).unwrap(); @@ -167,8 +167,8 @@ fn test_swaps_should_kick_start_if_process_was_killed() { alice_conf["skip_seednodes_check"] = true.into(); let mut mm_alice_dup = MarketMakerIt::start(alice_conf, "pass".to_string(), None).unwrap(); let (_alice_dup_dump_log, _alice_dup_dump_dashboard) = mm_dump(&mm_alice_dup.log_path); - log!("{:?}", block_on(enable_native(&mm_alice_dup, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_alice_dup, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_alice_dup, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice_dup, "MYCOIN1", &[], None))); block_on(mm_alice_dup.wait_for_log(50., |log| log.contains(&format!("Swap {} kick started.", uuid)))).unwrap(); } @@ -214,8 +214,8 @@ fn swap_should_not_kick_start_if_finished_during_waiting_for_file_lock( }); let mut mm_bob = MarketMakerIt::start(bob_conf, "pass".to_string(), None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[]))); - log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[]))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); block_on(mm_bob.wait_for_log(22., |log| { log.contains("Kick starting the swap 5acb0e63-8b26-469e-81df-7dd9e4a9ad15") })) diff --git a/mm2src/mm2_main/tests/integration_tests_common/mod.rs b/mm2src/mm2_main/tests/integration_tests_common/mod.rs index 75d95e8af5..327c7bbc8d 100644 --- a/mm2src/mm2_main/tests/integration_tests_common/mod.rs +++ b/mm2src/mm2_main/tests/integration_tests_common/mod.rs @@ -2,6 +2,7 @@ use common::executor::Timer; use common::log::LogLevel; use common::{block_on, log, now_ms, wait_until_ms}; use crypto::privkey::key_pair_from_seed; +use crypto::StandardHDCoinAddress; use mm2_main::mm2::{lp_main, LpMainParams}; use mm2_rpc::data::legacy::CoinInitResponse; use mm2_test_helpers::electrums::{morty_electrums, rick_electrums}; @@ -33,10 +34,16 @@ pub fn test_mm_start_impl() { } /// Ideally, this function should be replaced everywhere with `enable_electrum_json`. -pub async fn enable_electrum(mm: &MarketMakerIt, coin: &str, tx_history: bool, urls: &[&str]) -> CoinInitResponse { +pub async fn enable_electrum( + mm: &MarketMakerIt, + coin: &str, + tx_history: bool, + urls: &[&str], + path_to_address: Option, +) -> CoinInitResponse { use mm2_test_helpers::for_tests::enable_electrum as enable_electrum_impl; - let value = enable_electrum_impl(mm, coin, tx_history, urls).await; + let value = enable_electrum_impl(mm, coin, tx_history, urls, path_to_address).await; json::from_value(value).unwrap() } @@ -45,24 +52,33 @@ pub async fn enable_electrum_json( coin: &str, tx_history: bool, servers: Vec, + path_to_address: Option, ) -> CoinInitResponse { use mm2_test_helpers::for_tests::enable_electrum_json as enable_electrum_impl; - let value = enable_electrum_impl(mm, coin, tx_history, servers).await; + let value = enable_electrum_impl(mm, coin, tx_history, servers, path_to_address).await; json::from_value(value).unwrap() } -pub async fn enable_native(mm: &MarketMakerIt, coin: &str, urls: &[&str]) -> CoinInitResponse { - let value = enable_native_impl(mm, coin, urls).await; +pub async fn enable_native( + mm: &MarketMakerIt, + coin: &str, + urls: &[&str], + path_to_address: Option, +) -> CoinInitResponse { + let value = enable_native_impl(mm, coin, urls, path_to_address).await; json::from_value(value).unwrap() } pub async fn enable_coins_rick_morty_electrum(mm: &MarketMakerIt) -> HashMap<&'static str, CoinInitResponse> { let mut replies = HashMap::new(); - replies.insert("RICK", enable_electrum_json(mm, "RICK", false, rick_electrums()).await); + replies.insert( + "RICK", + enable_electrum_json(mm, "RICK", false, rick_electrums(), None).await, + ); replies.insert( "MORTY", - enable_electrum_json(mm, "MORTY", false, morty_electrums()).await, + enable_electrum_json(mm, "MORTY", false, morty_electrums(), None).await, ); replies } @@ -72,8 +88,9 @@ pub async fn enable_z_coin_light( coin: &str, electrums: &[&str], lightwalletd_urls: &[&str], + account: Option, ) -> CoinActivationResult { - let init = init_z_coin_light(mm, coin, electrums, lightwalletd_urls).await; + let init = init_z_coin_light(mm, coin, electrums, lightwalletd_urls, account).await; let init: RpcV2Response = json::from_value(init).unwrap(); let timeout = wait_until_ms(12000000); @@ -122,15 +139,19 @@ pub async fn enable_utxo_v2_electrum( pub async fn enable_coins_eth_electrum( mm: &MarketMakerIt, eth_urls: &[&str], + path_to_address: Option, ) -> HashMap<&'static str, CoinInitResponse> { let mut replies = HashMap::new(); - replies.insert("RICK", enable_electrum_json(mm, "RICK", false, rick_electrums()).await); + replies.insert( + "RICK", + enable_electrum_json(mm, "RICK", false, rick_electrums(), path_to_address.clone()).await, + ); replies.insert( "MORTY", - enable_electrum_json(mm, "MORTY", false, morty_electrums()).await, + enable_electrum_json(mm, "MORTY", false, morty_electrums(), path_to_address.clone()).await, ); - replies.insert("ETH", enable_native(mm, "ETH", eth_urls).await); - replies.insert("JST", enable_native(mm, "JST", eth_urls).await); + replies.insert("ETH", enable_native(mm, "ETH", eth_urls, path_to_address.clone()).await); + replies.insert("JST", enable_native(mm, "JST", eth_urls, path_to_address).await); replies } diff --git a/mm2src/mm2_main/tests/mm2_tests/bch_and_slp_tests.rs b/mm2src/mm2_main/tests/mm2_tests/bch_and_slp_tests.rs index cf66cb92ad..f4790b4b35 100644 --- a/mm2src/mm2_main/tests/mm2_tests/bch_and_slp_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/bch_and_slp_tests.rs @@ -1,5 +1,6 @@ use common::custom_futures::repeatable::{Ready, Retry}; use common::{block_on, log, repeatable}; +use crypto::StandardHDCoinAddress; use http::StatusCode; use itertools::Itertools; use mm2_test_helpers::for_tests::{disable_coin, enable_bch_with_tokens, enable_slp, my_tx_history_v2, sign_message, @@ -427,7 +428,7 @@ async fn test_bch_and_slp_testnet_history_impl() { let rpc_mode = UtxoRpcMode::electrum(T_BCH_ELECTRUMS); let tx_history = true; - let enable_bch = enable_bch_with_tokens(&mm, "tBCH", &[], rpc_mode, tx_history).await; + let enable_bch = enable_bch_with_tokens(&mm, "tBCH", &[], rpc_mode, tx_history, None).await; log!("enable_bch: {:?}", enable_bch); let history = wait_till_history_has_records(&mm, 4, "tBCH", None, TIMEOUT_S).await; log!("bch history: {:?}", history); @@ -596,7 +597,7 @@ fn test_sign_verify_message_slp() { log!("log path: {}", mm.log_path.display()); let rpc_mode = UtxoRpcMode::electrum(T_BCH_ELECTRUMS); - let enable_bch = block_on(enable_bch_with_tokens(&mm, "tBCH", &[], rpc_mode, false)); + let enable_bch = block_on(enable_bch_with_tokens(&mm, "tBCH", &[], rpc_mode, false, None)); log!("enable_bch: {:?}", enable_bch); let enable_usdf = block_on(enable_slp(&mm, "USDF")); @@ -626,15 +627,18 @@ fn test_sign_verify_message_slp() { /// Tested via [Electron-Cash-SLP](https://github.com/simpleledger/Electron-Cash-SLP). #[test] #[cfg(not(target_arch = "wasm32"))] -fn test_bch_and_slp_with_hd_account_id() { +fn test_bch_and_slp_with_enable_hd() { const TX_HISTORY: bool = false; let coins = json!([tbch_for_slp_conf(), tbch_usdf_conf()]); - // HD account 0 - - let hd_account_id = 0; - let conf_0 = Mm2TestConf::seednode_with_hd_account(BIP39_PASSPHRASE, hd_account_id, &coins); + // HD account 0 and change 0 and address_index 0 + let path_to_address = StandardHDCoinAddress { + account: 0, + is_change: false, + address_index: 0, + }; + let conf_0 = Mm2TestConf::seednode_with_hd_account(BIP39_PASSPHRASE, &coins); let mm_hd_0 = MarketMakerIt::start(conf_0.conf, conf_0.rpc_password, None).unwrap(); let rpc_mode = UtxoRpcMode::electrum(T_BCH_ELECTRUMS); @@ -644,6 +648,7 @@ fn test_bch_and_slp_with_hd_account_id() { &["USDF"], rpc_mode, TX_HISTORY, + Some(path_to_address), )); let activation_result: RpcV2Response = json::from_value(activation_result).unwrap(); @@ -663,10 +668,13 @@ fn test_bch_and_slp_with_hd_account_id() { .unwrap(); assert_eq!(slp_addr, "slptest:qpylzql7gzh6yctm7uslsz5qufl44gk2tsfnl7m9uj"); - // HD account 1 - - let hd_account_id = 1; - let conf_1 = Mm2TestConf::seednode_with_hd_account(BIP39_PASSPHRASE, hd_account_id, &coins); + // HD account 0 and change 0 and address_index 1 + let path_to_address = StandardHDCoinAddress { + account: 0, + is_change: false, + address_index: 1, + }; + let conf_1 = Mm2TestConf::seednode_with_hd_account(BIP39_PASSPHRASE, &coins); let mm_hd_1 = MarketMakerIt::start(conf_1.conf, conf_1.rpc_password, None).unwrap(); let rpc_mode = UtxoRpcMode::electrum(T_BCH_ELECTRUMS); @@ -676,6 +684,7 @@ fn test_bch_and_slp_with_hd_account_id() { &["USDF"], rpc_mode, TX_HISTORY, + Some(path_to_address), )); let activation_result: RpcV2Response = json::from_value(activation_result).unwrap(); @@ -694,4 +703,40 @@ fn test_bch_and_slp_with_hd_account_id() { .exactly_one() .unwrap(); assert_eq!(slp_addr, "slptest:qpyhwc7shd5hlul8zg0snmaptaa9q9yc4q9uzddky0"); + + // HD account 7 and change 1 and address_index 77 + let path_to_address = StandardHDCoinAddress { + account: 7, + is_change: true, + address_index: 77, + }; + let conf_1 = Mm2TestConf::seednode_with_hd_account(BIP39_PASSPHRASE, &coins); + let mm_hd_1 = MarketMakerIt::start(conf_1.conf, conf_1.rpc_password, None).unwrap(); + + let rpc_mode = UtxoRpcMode::electrum(T_BCH_ELECTRUMS); + let activation_result = block_on(enable_bch_with_tokens( + &mm_hd_1, + "tBCH", + &["USDF"], + rpc_mode, + TX_HISTORY, + Some(path_to_address), + )); + + let activation_result: RpcV2Response = json::from_value(activation_result).unwrap(); + let (bch_addr, _) = activation_result + .result + .bch_addresses_infos + .into_iter() + .exactly_one() + .unwrap(); + assert_eq!(bch_addr, "bchtest:qzghm7m4v2jyn3dz4qcfdmzg9xnhqvlgeqlx6ru72p"); + + let (slp_addr, _) = activation_result + .result + .slp_addresses_infos + .into_iter() + .exactly_one() + .unwrap(); + assert_eq!(slp_addr, "slptest:qzghm7m4v2jyn3dz4qcfdmzg9xnhqvlgeqyjacxfcu"); } diff --git a/mm2src/mm2_main/tests/mm2_tests/best_orders_tests.rs b/mm2src/mm2_main/tests/mm2_tests/best_orders_tests.rs index 41a510c63c..fcf6629642 100644 --- a/mm2src/mm2_main/tests/mm2_tests/best_orders_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/best_orders_tests.rs @@ -42,7 +42,7 @@ fn test_best_orders() { log!("Bob log path: {}", mm_bob.log_path.display()); // Enable coins on Bob side. Print the replies in case we need the "address". - let bob_coins = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES)); + let bob_coins = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)); log!("enable_coins (bob): {:?}", bob_coins); // issue sell request on Bob side by setting base/rel price log!("Issue bob sell requests"); @@ -207,7 +207,7 @@ fn test_best_orders_v2_by_number() { log!("Bob log path: {:?}", [mm_bob.log_path.display()]); // Enable coins on Bob side. Print the replies in case we need the "address". - let bob_coins = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES)); + let bob_coins = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)); log!("enable_coins (bob) {:?}", [bob_coins]); // issue sell request on Bob side by setting base/rel price log!("Issue bob sell requests"); @@ -330,7 +330,7 @@ fn test_best_orders_v2_by_volume() { log!("Bob log path: {:?}", [mm_bob.log_path.display()]); // Enable coins on Bob side. Print the replies in case we need the "address". - let bob_coins = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES)); + let bob_coins = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)); log!("enable_coins (bob): {:?}", [bob_coins]); // issue sell request on Bob side by setting base/rel price log!("Issue bob sell requests"); @@ -444,7 +444,7 @@ fn test_best_orders_v2_exclude_mine() { ) .unwrap(); - let _ = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES)); + let _ = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)); let bob_orders = [ ("RICK", "MORTY", "0.9", "0.9", None), ("RICK", "MORTY", "0.8", "0.9", None), @@ -485,7 +485,7 @@ fn test_best_orders_v2_exclude_mine() { ) .unwrap(); - let _ = block_on(enable_coins_eth_electrum(&mm_alice, ETH_DEV_NODES)); + let _ = block_on(enable_coins_eth_electrum(&mm_alice, ETH_DEV_NODES, None)); let alice_orders = [("RICK", "MORTY", "0.85", "1", None)]; let mut alice_order_ids = BTreeSet::::new(); for (base, rel, price, volume, min_volume) in alice_orders.iter() { @@ -703,7 +703,7 @@ fn test_best_orders_filter_response() { log!("Bob log path: {}", mm_bob.log_path.display()); // Enable coins on Bob side. Print the replies in case we need the "address". - let bob_coins = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES)); + let bob_coins = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)); log!("enable_coins (bob): {:?}", bob_coins); // issue sell request on Bob side by setting base/rel price log!("Issue bob sell requests"); @@ -992,10 +992,10 @@ fn best_orders_must_return_duplicate_for_orderbook_tickers() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - let t_btc_bob = block_on(enable_electrum(&mm_bob, "tBTC", false, TBTC_ELECTRUMS)); + let t_btc_bob = block_on(enable_electrum(&mm_bob, "tBTC", false, TBTC_ELECTRUMS, None)); log!("Bob enable tBTC: {:?}", t_btc_bob); - let rick_bob = block_on(enable_electrum(&mm_bob, "RICK", false, RICK_ELECTRUM_ADDRS)); + let rick_bob = block_on(enable_electrum(&mm_bob, "RICK", false, RICK_ELECTRUM_ADDRS, None)); log!("Bob enable RICK: {:?}", rick_bob); // issue sell request on Bob side by setting base/rel price @@ -1146,7 +1146,7 @@ fn zhtlc_best_orders() { log!("bob_zombie_cache_path {}", bob_zombie_cache_path.display()); std::fs::copy("./mm2src/coins/for_tests/ZOMBIE_CACHE.db", bob_zombie_cache_path).unwrap(); - block_on(enable_electrum_json(&mm_bob, "RICK", false, rick_electrums())); + block_on(enable_electrum_json(&mm_bob, "RICK", false, rick_electrums(), None)); block_on(enable_z_coin(&mm_bob, "ZOMBIE")); let set_price_json = json!({ diff --git a/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs b/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs index 9bbc37f056..561e97774d 100644 --- a/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs +++ b/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs @@ -94,7 +94,7 @@ pub async fn trade_base_rel_iris( ) .await ); - dbg!(enable_electrum(&mm_bob, "RICK", false, RICK_ELECTRUM_ADDRS).await); + dbg!(enable_electrum(&mm_bob, "RICK", false, RICK_ELECTRUM_ADDRS, None).await); dbg!( enable_tendermint( @@ -106,7 +106,7 @@ pub async fn trade_base_rel_iris( ) .await ); - dbg!(enable_electrum(&mm_alice, "RICK", false, RICK_ELECTRUM_ADDRS).await); + dbg!(enable_electrum(&mm_alice, "RICK", false, RICK_ELECTRUM_ADDRS, None).await); dbg!(enable_eth_coin(&mm_bob, "tBNB", TBNB_URLS, TBNB_SWAP_CONTRACT, None, false).await); dbg!(enable_eth_coin(&mm_alice, "tBNB", TBNB_URLS, TBNB_SWAP_CONTRACT, None, false).await); diff --git a/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs b/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs index 4fde665bd6..5e69a89771 100644 --- a/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs @@ -120,7 +120,13 @@ fn start_lightning_nodes(enable_0_confs: bool) -> (MarketMakerIt, MarketMakerIt, let (_dump_log, _dump_dashboard) = mm_node_1.mm_dump(); log!("Node 1 log path: {}", mm_node_1.log_path.display()); - let electrum = block_on(enable_electrum(&mm_node_1, "tBTC-TEST-segwit", false, T_BTC_ELECTRUMS)); + let electrum = block_on(enable_electrum( + &mm_node_1, + "tBTC-TEST-segwit", + false, + T_BTC_ELECTRUMS, + None, + )); log!("Node 1 tBTC address: {}", electrum.address); let enable_lightning_1 = block_on(enable_lightning(&mm_node_1, "tBTC-TEST-lightning", 600)); @@ -144,7 +150,13 @@ fn start_lightning_nodes(enable_0_confs: bool) -> (MarketMakerIt, MarketMakerIt, let (_dump_log, _dump_dashboard) = mm_node_2.mm_dump(); log!("Node 2 log path: {}", mm_node_2.log_path.display()); - let electrum = block_on(enable_electrum(&mm_node_2, "tBTC-TEST-segwit", false, T_BTC_ELECTRUMS)); + let electrum = block_on(enable_electrum( + &mm_node_2, + "tBTC-TEST-segwit", + false, + T_BTC_ELECTRUMS, + None, + )); log!("Node 2 tBTC address: {}", electrum.address); let enable_lightning_2 = block_on(enable_lightning(&mm_node_2, "tBTC-TEST-lightning", 600)); @@ -376,7 +388,7 @@ fn test_enable_lightning() { let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("log path: {}", mm.log_path.display()); - let _electrum = block_on(enable_electrum(&mm, "tBTC-TEST-segwit", false, T_BTC_ELECTRUMS)); + let _electrum = block_on(enable_electrum(&mm, "tBTC-TEST-segwit", false, T_BTC_ELECTRUMS, None)); let enable_lightning_coin = block_on(enable_lightning(&mm, "tBTC-TEST-lightning", 600)); assert_eq!(&enable_lightning_coin.platform_coin, "tBTC-TEST-segwit"); @@ -1063,7 +1075,7 @@ fn test_sign_verify_message_lightning() { let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("log path: {}", mm.log_path.display()); - block_on(enable_electrum(&mm, "tBTC-TEST-segwit", false, T_BTC_ELECTRUMS)); + block_on(enable_electrum(&mm, "tBTC-TEST-segwit", false, T_BTC_ELECTRUMS, None)); block_on(enable_lightning(&mm, "tBTC-TEST-lightning", 600)); let response = block_on(sign_message(&mm, "tBTC-TEST-lightning")); diff --git a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs index 02e0b53b04..2bfff0f3b6 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -15,14 +15,16 @@ use mm2_test_helpers::for_tests::check_stats_swap_status; use mm2_test_helpers::for_tests::{btc_segwit_conf, btc_with_spv_conf, btc_with_sync_starting_header, check_recent_swaps, enable_eth_coin, enable_qrc20, eth_jst_testnet_conf, eth_testnet_conf, find_metrics_in_json, from_env_file, get_shared_db_id, mm_spat, - morty_conf, rick_conf, sign_message, start_swaps, tbtc_with_spv_conf, - test_qrc20_history_impl, tqrc20_conf, verify_message, + morty_conf, rick_conf, sign_message, start_swaps, tbtc_segwit_conf, + tbtc_with_spv_conf, test_qrc20_history_impl, tqrc20_conf, verify_message, wait_for_swap_contract_negotiation, wait_for_swap_negotiation_failure, wait_for_swaps_finish_and_check_status, wait_till_history_has_records, MarketMakerIt, Mm2InitPrivKeyPolicy, Mm2TestConf, Mm2TestConfForSwap, RaiiDump, ETH_DEV_NODES, ETH_DEV_SWAP_CONTRACT, ETH_DEV_TOKEN_CONTRACT, ETH_MAINNET_NODE, - ETH_MAINNET_SWAP_CONTRACT, MORTY, QRC20_ELECTRUMS, RICK, RICK_ELECTRUM_ADDRS}; + ETH_MAINNET_SWAP_CONTRACT, MORTY, QRC20_ELECTRUMS, RICK, RICK_ELECTRUM_ADDRS, + TBTC_ELECTRUMS}; +use crypto::StandardHDCoinAddress; use mm2_test_helpers::get_passphrase; use mm2_test_helpers::structs::*; use serde_json::{self as json, json, Value as Json}; @@ -230,11 +232,17 @@ fn test_my_balance() { let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("log path: {}", mm.log_path.display()); // Enable RICK. - let json = block_on(enable_electrum(&mm, "RICK", false, &[ - "electrum1.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum3.cipig.net:10017", - ])); + let json = block_on(enable_electrum( + &mm, + "RICK", + false, + &[ + "electrum1.cipig.net:10017", + "electrum2.cipig.net:10017", + "electrum3.cipig.net:10017", + ], + None, + )); assert_eq!(json.balance, "7.777".parse().unwrap()); let my_balance = block_on(mm.rpc(&json! ({ @@ -416,9 +424,11 @@ fn test_check_balance_on_order_post() { // Enable coins. Print the replies in case we need the "address". log!( "enable_coins (bob): {:?}", - block_on(enable_coins_eth_electrum(&mm, &[ - "https://mainnet.infura.io/v3/c01c1b4cf66642528547624e1d6d9d6b" - ])) + block_on(enable_coins_eth_electrum( + &mm, + &["https://mainnet.infura.io/v3/c01c1b4cf66642528547624e1d6d9d6b"], + None + )), ); // issue sell request by setting base/rel price @@ -597,11 +607,17 @@ fn test_mmrpc_v2() { let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("Log path: {}", mm.log_path.display()); - let _electrum = block_on(enable_electrum(&mm, "RICK", false, &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - ])); + let _electrum = block_on(enable_electrum( + &mm, + "RICK", + false, + &[ + "electrum3.cipig.net:10017", + "electrum2.cipig.net:10017", + "electrum1.cipig.net:10017", + ], + None, + )); // no `userpass` let withdraw = block_on(mm.rpc(&json! ({ @@ -725,9 +741,12 @@ fn test_rpc_password_from_json_no_userpass() { /// Trades few pairs concurrently to speed up the process and also act like "load" test /// /// Please note that it +#[allow(clippy::too_many_arguments)] async fn trade_base_rel_electrum( bob_priv_key_policy: Mm2InitPrivKeyPolicy, alice_priv_key_policy: Mm2InitPrivKeyPolicy, + bob_path_to_address: Option, + alice_path_to_address: Option, pairs: &[(&'static str, &'static str)], maker_price: f64, taker_price: f64, @@ -741,7 +760,7 @@ async fn trade_base_rel_electrum( {"coin":"ZOMBIE","asset":"ZOMBIE","fname":"ZOMBIE (TESTCOIN)","txversion":4,"overwintered":1,"mm2":1,"protocol":{"type":"ZHTLC"},"required_confirmations":0}, ]); - let bob_conf = Mm2TestConfForSwap::bob_conf_with_policy(bob_priv_key_policy, &coins); + let bob_conf = Mm2TestConfForSwap::bob_conf_with_policy(&bob_priv_key_policy, &coins); let mut mm_bob = MarketMakerIt::start_async(bob_conf.conf, bob_conf.rpc_password, None) .await .unwrap(); @@ -754,7 +773,7 @@ async fn trade_base_rel_electrum( Timer::sleep(1.).await; - let alice_conf = Mm2TestConfForSwap::alice_conf_with_policy(alice_priv_key_policy, &coins, &mm_bob.my_seed_addr()); + let alice_conf = Mm2TestConfForSwap::alice_conf_with_policy(&alice_priv_key_policy, &coins, &mm_bob.my_seed_addr()); let mut mm_alice = MarketMakerIt::start_async(alice_conf.conf, alice_conf.rpc_password, None) .await .unwrap(); @@ -791,11 +810,11 @@ async fn trade_base_rel_electrum( log!("enable ZOMBIE alice {:?}", zombie_alice); } // Enable coins on Bob side. Print the replies in case we need the address. - let rc = enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES).await; + let rc = enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, bob_path_to_address).await; log!("enable_coins (bob): {:?}", rc); // Enable coins on Alice side. Print the replies in case we need the address. - let rc = enable_coins_eth_electrum(&mm_alice, ETH_DEV_NODES).await; + let rc = enable_coins_eth_electrum(&mm_alice, ETH_DEV_NODES, alice_path_to_address).await; log!("enable_coins (alice): {:?}", rc); let uuids = start_swaps(&mut mm_bob, &mut mm_alice, pairs, maker_price, taker_price, volume).await; @@ -873,11 +892,18 @@ async fn trade_base_rel_electrum( #[cfg(not(target_arch = "wasm32"))] fn trade_test_electrum_and_eth_coins() { let bob_policy = Mm2InitPrivKeyPolicy::Iguana; - let alice_policy = Mm2InitPrivKeyPolicy::GlobalHDAccount(0); + let alice_policy = Mm2InitPrivKeyPolicy::GlobalHDAccount; + let alice_path_to_address = StandardHDCoinAddress { + account: 0, + is_change: false, + address_index: 0, + }; let pairs = &[("ETH", "JST")]; block_on(trade_base_rel_electrum( bob_policy, alice_policy, + None, + Some(alice_path_to_address), pairs, 1., 2., @@ -891,13 +917,23 @@ fn trade_test_electrum_rick_zombie() { let bob_policy = Mm2InitPrivKeyPolicy::Iguana; let alice_policy = Mm2InitPrivKeyPolicy::Iguana; let pairs = &[("RICK", "ZOMBIE")]; - block_on(trade_base_rel_electrum(bob_policy, alice_policy, pairs, 1., 2., 0.1)); + block_on(trade_base_rel_electrum( + bob_policy, + alice_policy, + None, + None, + pairs, + 1., + 2., + 0.1, + )); } #[cfg(not(target_arch = "wasm32"))] fn withdraw_and_send( mm: &MarketMakerIt, coin: &str, + from: Option, to: &str, enable_res: &HashMap<&'static str, CoinInitResponse>, expected_bal_change: &str, @@ -905,14 +941,16 @@ fn withdraw_and_send( ) { use std::ops::Sub; - use coins::TxFeeDetails; + use coins::{TxFeeDetails, WithdrawFrom}; + let from = from.map(WithdrawFrom::HDWalletAddress); let withdraw = block_on(mm.rpc(&json! ({ "mmrpc": "2.0", "userpass": mm.userpass, "method": "withdraw", "params": { "coin": coin, + "from": from, "to": to, "amount": amount, }, @@ -925,7 +963,7 @@ fn withdraw_and_send( json::from_str(&withdraw.1).expect("Expected 'RpcSuccessResponse'"); let tx_details = res.result; - let from = addr_from_enable(enable_res, coin).to_owned(); + let from_str = addr_from_enable(enable_res, coin).to_owned(); let mut expected_bal_change = BigDecimal::from_str(expected_bal_change).expect("!BigDecimal::from_str"); let fee_details: TxFeeDetails = json::from_value(tx_details.fee_details).unwrap(); @@ -938,7 +976,10 @@ fn withdraw_and_send( assert_eq!(tx_details.to, vec![to.to_owned()]); assert_eq!(tx_details.my_balance_change, expected_bal_change); - assert_eq!(tx_details.from, vec![from]); + // Todo: Should check the from address for withdraws from another HD wallet address when there is an RPC method for addresses + if from.is_none() { + assert_eq!(tx_details.from, vec![from_str]); + } let send = block_on(mm.rpc(&json! ({ "userpass": mm.userpass, @@ -987,20 +1028,27 @@ fn test_withdraw_and_send() { // wait until RPC API is active // Enable coins. Print the replies in case we need the address. - let mut enable_res = block_on(enable_coins_eth_electrum(&mm_alice, ETH_DEV_NODES)); + let mut enable_res = block_on(enable_coins_eth_electrum(&mm_alice, ETH_DEV_NODES, None)); enable_res.insert( "MORTY_SEGWIT", - block_on(enable_electrum(&mm_alice, "MORTY_SEGWIT", false, &[ - "electrum1.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum3.cipig.net:10018", - ])), + block_on(enable_electrum( + &mm_alice, + "MORTY_SEGWIT", + false, + &[ + "electrum1.cipig.net:10018", + "electrum2.cipig.net:10018", + "electrum3.cipig.net:10018", + ], + None, + )), ); log!("enable_coins (alice): {:?}", enable_res); withdraw_and_send( &mm_alice, "MORTY", + None, "RJTYiYeJ8eVvJ53n2YbrVmxWNNMVZjDGLh", &enable_res, "-0.00101", @@ -1009,6 +1057,7 @@ fn test_withdraw_and_send() { withdraw_and_send( &mm_alice, "ETH", + None, "0x4b2d0d6c2c785217457B69B922A2A9cEA98f71E9", &enable_res, "-0.001", @@ -1019,6 +1068,7 @@ fn test_withdraw_and_send() { withdraw_and_send( &mm_alice, "JST", + None, "0x4b2d0d6c2c785217457B69B922A2A9cEA98f71E9", &enable_res, "-0.001", @@ -1104,6 +1154,89 @@ fn test_withdraw_and_send() { block_on(mm_alice.stop()).unwrap(); } +// This test is ignored because it requires refilling addresses with coins +#[test] +#[ignore] +#[cfg(not(target_arch = "wasm32"))] +fn test_withdraw_and_send_hd() { + const TX_HISTORY: bool = false; + const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + + let coins = json!([rick_conf(), tbtc_segwit_conf(), eth_testnet_conf()]); + + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_dump_log, _dump_dashboard) = mm_hd.mm_dump(); + log!("log path: {}", mm_hd.log_path.display()); + + let rick = block_on(enable_electrum(&mm_hd, "RICK", TX_HISTORY, RICK_ELECTRUM_ADDRS, None)); + assert_eq!(rick.address, "RXNtAyDSsY3DS3VxTpJegzoHU9bUX54j56"); + let mut rick_enable_res = HashMap::new(); + rick_enable_res.insert("RICK", rick); + + let tbtc_segwit = block_on(enable_electrum(&mm_hd, "tBTC-Segwit", TX_HISTORY, TBTC_ELECTRUMS, None)); + assert_eq!(tbtc_segwit.address, "tb1q7z9vzf8wpp9cks0l4nj5v28zf7jt56kuekegh5"); + let mut tbtc_segwit_enable_res = HashMap::new(); + tbtc_segwit_enable_res.insert("tBTC-Segwit", tbtc_segwit); + + // Enable ETH with HD account 0, change address 0, index 1 to try to withdraw from index 0 which has funds + let eth = block_on(enable_native( + &mm_hd, + "ETH", + ETH_DEV_NODES, + Some(StandardHDCoinAddress { + account: 0, + is_change: false, + address_index: 1, + }), + )); + assert_eq!(eth.address, "0xDe841899aB4A22E23dB21634e54920aDec402397"); + let mut eth_enable_res = HashMap::new(); + eth_enable_res.insert("ETH", eth); + + // Withdraw from HD account 0, change address 0, index 1 + let mut from_account_address = StandardHDCoinAddress { + account: 0, + is_change: false, + address_index: 1, + }; + + withdraw_and_send( + &mm_hd, + "RICK", + Some(from_account_address.clone()), + "RJTYiYeJ8eVvJ53n2YbrVmxWNNMVZjDGLh", + &rick_enable_res, + "-0.00101", + 0.001, + ); + + withdraw_and_send( + &mm_hd, + "tBTC-Segwit", + Some(from_account_address.clone()), + "tb1q7z9vzf8wpp9cks0l4nj5v28zf7jt56kuekegh5", + &tbtc_segwit_enable_res, + "-0.00100144", + 0.001, + ); + + from_account_address.address_index = 0; + withdraw_and_send( + &mm_hd, + "ETH", + Some(from_account_address), + "0x4b2d0d6c2c785217457B69B922A2A9cEA98f71E9", + ð_enable_res, + "-0.001", + 0.001, + ); + log!("Wait for the ETH payment to be sent"); + thread::sleep(Duration::from_secs(15)); + + block_on(mm_hd.stop()).unwrap(); +} + #[test] #[cfg(not(target_arch = "wasm32"))] fn test_tbtc_withdraw_to_cashaddresses_should_fail() { @@ -1228,11 +1361,17 @@ fn test_withdraw_legacy() { let mut enable_res = block_on(enable_coins_rick_morty_electrum(&mm_alice)); enable_res.insert( "MORTY_SEGWIT", - block_on(enable_electrum(&mm_alice, "MORTY_SEGWIT", false, &[ - "electrum1.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum3.cipig.net:10018", - ])), + block_on(enable_electrum( + &mm_alice, + "MORTY_SEGWIT", + false, + &[ + "electrum1.cipig.net:10018", + "electrum2.cipig.net:10018", + "electrum3.cipig.net:10018", + ], + None, + )), ); log!("enable_coins (alice): {:?}", enable_res); @@ -1462,11 +1601,17 @@ fn test_order_errors_when_base_equal_rel() { .unwrap(); let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("Log path: {}", mm.log_path.display()); - block_on(enable_electrum(&mm, "RICK", false, &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - ])); + block_on(enable_electrum( + &mm, + "RICK", + false, + &[ + "electrum3.cipig.net:10017", + "electrum2.cipig.net:10017", + "electrum1.cipig.net:10017", + ], + None, + )); let rc = block_on(mm.rpc(&json! ({ "userpass": mm.userpass, @@ -1528,7 +1673,7 @@ fn startup_passphrase(passphrase: &str, expected_address: &str) { { log!("Log path: {}", mm.log_path.display()) } - let enable = block_on(enable_electrum(&mm, "KMD", false, &["electrum1.cipig.net:10001"])); + let enable = block_on(enable_electrum(&mm, "KMD", false, &["electrum1.cipig.net:10001"], None)); assert_eq!(expected_address, enable.address); block_on(mm.stop()).unwrap(); } @@ -1884,21 +2029,33 @@ fn test_electrum_enable_conn_errors() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); // Using working servers and few else with random ports to trigger "connection refused" - block_on(enable_electrum(&mm_bob, "RICK", false, &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - "electrum1.cipig.net:60017", - "electrum1.cipig.net:60018", - ])); + block_on(enable_electrum( + &mm_bob, + "RICK", + false, + &[ + "electrum3.cipig.net:10017", + "electrum2.cipig.net:10017", + "electrum1.cipig.net:10017", + "electrum1.cipig.net:60017", + "electrum1.cipig.net:60018", + ], + None, + )); // use random domain name to trigger name is not resolved - block_on(enable_electrum(&mm_bob, "MORTY", false, &[ - "electrum3.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum1.cipig.net:10018", - "random-electrum-domain-name1.net:60017", - "random-electrum-domain-name2.net:60017", - ])); + block_on(enable_electrum( + &mm_bob, + "MORTY", + false, + &[ + "electrum3.cipig.net:10018", + "electrum2.cipig.net:10018", + "electrum1.cipig.net:10018", + "random-electrum-domain-name1.net:60017", + "random-electrum-domain-name2.net:60017", + ], + None, + )); } #[test] @@ -1930,18 +2087,30 @@ fn test_order_should_not_be_displayed_when_node_is_down() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - let electrum_rick = block_on(enable_electrum(&mm_bob, "RICK", false, &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - ])); + let electrum_rick = block_on(enable_electrum( + &mm_bob, + "RICK", + false, + &[ + "electrum3.cipig.net:10017", + "electrum2.cipig.net:10017", + "electrum1.cipig.net:10017", + ], + None, + )); log!("Bob enable RICK {:?}", electrum_rick); - let electrum_morty = block_on(enable_electrum(&mm_bob, "MORTY", false, &[ - "electrum3.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum1.cipig.net:10018", - ])); + let electrum_morty = block_on(enable_electrum( + &mm_bob, + "MORTY", + false, + &[ + "electrum3.cipig.net:10018", + "electrum2.cipig.net:10018", + "electrum1.cipig.net:10018", + ], + None, + )); log!("Bob enable MORTY {:?}", electrum_morty); let mm_alice = MarketMakerIt::start( @@ -1964,18 +2133,30 @@ fn test_order_should_not_be_displayed_when_node_is_down() { let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); - let electrum_rick = block_on(enable_electrum(&mm_alice, "RICK", false, &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - ])); + let electrum_rick = block_on(enable_electrum( + &mm_alice, + "RICK", + false, + &[ + "electrum3.cipig.net:10017", + "electrum2.cipig.net:10017", + "electrum1.cipig.net:10017", + ], + None, + )); log!("Alice enable RICK {:?}", electrum_rick); - let electrum_morty = block_on(enable_electrum(&mm_alice, "MORTY", false, &[ - "electrum3.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum1.cipig.net:10018", - ])); + let electrum_morty = block_on(enable_electrum( + &mm_alice, + "MORTY", + false, + &[ + "electrum3.cipig.net:10018", + "electrum2.cipig.net:10018", + "electrum1.cipig.net:10018", + ], + None, + )); log!("Alice enable MORTY {:?}", electrum_morty); // issue sell request on Bob side by setting base/rel price @@ -2058,18 +2239,30 @@ fn test_own_orders_should_not_be_removed_from_orderbook() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - let electrum_rick = block_on(enable_electrum(&mm_bob, "RICK", false, &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - ])); + let electrum_rick = block_on(enable_electrum( + &mm_bob, + "RICK", + false, + &[ + "electrum3.cipig.net:10017", + "electrum2.cipig.net:10017", + "electrum1.cipig.net:10017", + ], + None, + )); log!("Bob enable RICK {:?}", electrum_rick); - let electrum_morty = block_on(enable_electrum(&mm_bob, "MORTY", false, &[ - "electrum3.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum1.cipig.net:10018", - ])); + let electrum_morty = block_on(enable_electrum( + &mm_bob, + "MORTY", + false, + &[ + "electrum3.cipig.net:10018", + "electrum2.cipig.net:10018", + "electrum1.cipig.net:10018", + ], + None, + )); log!("Bob enable MORTY {:?}", electrum_morty); // issue sell request on Bob side by setting base/rel price @@ -2144,7 +2337,7 @@ fn test_show_priv_key() { log!("Log path: {}", mm.log_path.display()); log!( "enable_coins: {:?}", - block_on(enable_coins_eth_electrum(&mm, ETH_DEV_NODES)) + block_on(enable_coins_eth_electrum(&mm, ETH_DEV_NODES, None)) ); check_priv_key(&mm, "RICK", "UvCjJf4dKSs2vFGVtCnUTAhR5FTZGdg43DDRa9s7s5DV1sSDX14g"); @@ -2331,7 +2524,7 @@ fn setprice_buy_sell_too_low_volume() { let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("Log path: {}", mm.log_path.display()); - let enable = block_on(enable_coins_eth_electrum(&mm, ETH_DEV_NODES)); + let enable = block_on(enable_coins_eth_electrum(&mm, ETH_DEV_NODES, None)); log!("{:?}", enable); check_too_low_volume_order_creation_fails(&mm, "MORTY", "ETH"); @@ -2367,7 +2560,10 @@ fn test_fill_or_kill_taker_order_should_not_transform_to_maker() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - log!("{:?}", block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES))); + log!( + "{:?}", + block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) + ); log!("Issue bob ETH/JST sell request"); let rc = block_on(mm_bob.rpc(&json! ({ @@ -2431,7 +2627,10 @@ fn test_gtc_taker_order_should_transform_to_maker() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - log!("{:?}", block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES))); + log!( + "{:?}", + block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) + ); log!("Issue bob ETH/JST sell request"); let rc = block_on(mm_bob.rpc(&json! ({ @@ -2501,7 +2700,10 @@ fn test_set_price_must_save_order_to_db() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - log!("{:?}", block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES))); + log!( + "{:?}", + block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) + ); log!("Issue bob ETH/JST sell request"); let rc = block_on(mm_bob.rpc(&json! ({ @@ -2551,7 +2753,10 @@ fn test_set_price_response_format() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - log!("{:?}", block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES))); + log!( + "{:?}", + block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) + ); log!("Issue bob ETH/JST sell request"); let rc = block_on(mm_bob.rpc(&json! ({ @@ -2804,11 +3009,17 @@ fn test_metrics_method() { let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("log path: {}", mm.log_path.display()); - let _electrum = block_on(enable_electrum(&mm, "RICK", false, &[ - "electrum1.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum3.cipig.net:10017", - ])); + let _electrum = block_on(enable_electrum( + &mm, + "RICK", + false, + &[ + "electrum1.cipig.net:10017", + "electrum2.cipig.net:10017", + "electrum3.cipig.net:10017", + ], + None, + )); let metrics = request_metrics(&mm); assert!(!metrics.metrics.is_empty()); @@ -2861,11 +3072,17 @@ fn test_electrum_tx_history() { log!("log path: {}", mm.log_path.display()); // Enable RICK electrum client with tx_history loop. - let electrum = block_on(enable_electrum(&mm, "RICK", true, &[ - "electrum1.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum3.cipig.net:10017", - ])); + let electrum = block_on(enable_electrum( + &mm, + "RICK", + true, + &[ + "electrum1.cipig.net:10017", + "electrum2.cipig.net:10017", + "electrum3.cipig.net:10017", + ], + None, + )); // Wait till tx_history will not be loaded block_on(mm.wait_for_log(500., |log| log.contains("history has been loaded successfully"))).unwrap(); @@ -2884,6 +3101,7 @@ fn test_electrum_tx_history() { withdraw_and_send( &mm, "RICK", + None, "RRYmiZSDo3UdHHqj1rLKf8cbJroyv9NxXw", &enable_res, "-0.00001", @@ -2954,13 +3172,19 @@ fn test_convert_utxo_address() { let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("log path: {}", mm.log_path.display()); - let _electrum = block_on(enable_electrum(&mm, "BCH", false, &[ - "electroncash.de:50003", - "tbch.loping.net:60001", - "blackie.c3-soft.com:60001", - "bch0.kister.net:51001", - "testnet.imaginary.cash:50001", - ])); + let _electrum = block_on(enable_electrum( + &mm, + "BCH", + false, + &[ + "electroncash.de:50003", + "tbch.loping.net:60001", + "blackie.c3-soft.com:60001", + "bch0.kister.net:51001", + "testnet.imaginary.cash:50001", + ], + None, + )); // test standard to cashaddress let rc = block_on(mm.rpc(&json! ({ @@ -3091,11 +3315,17 @@ fn test_convert_segwit_address() { let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("log path: {}", mm.log_path.display()); - let _electrum = block_on(enable_electrum(&mm, "tBTC", false, &[ - "electrum1.cipig.net:10068", - "electrum2.cipig.net:10068", - "electrum3.cipig.net:10068", - ])); + let _electrum = block_on(enable_electrum( + &mm, + "tBTC", + false, + &[ + "electrum1.cipig.net:10068", + "electrum2.cipig.net:10068", + "electrum3.cipig.net:10068", + ], + None, + )); // test standard to segwit let rc = block_on(mm.rpc(&json! ({ @@ -3206,7 +3436,7 @@ fn test_convert_eth_address() { let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("log path: {}", mm.log_path.display()); - block_on(enable_native(&mm, "ETH", ETH_DEV_NODES)); + block_on(enable_native(&mm, "ETH", ETH_DEV_NODES, None)); // test single-case to mixed-case let rc = block_on(mm.rpc(&json! ({ @@ -3310,11 +3540,17 @@ fn test_add_delegation_qtum() { ) .unwrap(); - let json = block_on(enable_electrum(&mm, "tQTUM", false, &[ - "electrum1.cipig.net:10071", - "electrum2.cipig.net:10071", - "electrum3.cipig.net:10071", - ])); + let json = block_on(enable_electrum( + &mm, + "tQTUM", + false, + &[ + "electrum1.cipig.net:10071", + "electrum2.cipig.net:10071", + "electrum3.cipig.net:10071", + ], + None, + )); println!("{}", json.balance); let rc = block_on(mm.rpc(&json!({ @@ -3395,11 +3631,17 @@ fn test_remove_delegation_qtum() { ) .unwrap(); - let json = block_on(enable_electrum(&mm, "tQTUM", false, &[ - "electrum1.cipig.net:10071", - "electrum2.cipig.net:10071", - "electrum3.cipig.net:10071", - ])); + let json = block_on(enable_electrum( + &mm, + "tQTUM", + false, + &[ + "electrum1.cipig.net:10071", + "electrum2.cipig.net:10071", + "electrum3.cipig.net:10071", + ], + None, + )); println!("{}", json.balance); let rc = block_on(mm.rpc(&json!({ @@ -3455,11 +3697,17 @@ fn test_get_staking_infos_qtum() { ) .unwrap(); - let json = block_on(enable_electrum(&mm, "tQTUM", false, &[ - "electrum1.cipig.net:10071", - "electrum2.cipig.net:10071", - "electrum3.cipig.net:10071", - ])); + let json = block_on(enable_electrum( + &mm, + "tQTUM", + false, + &[ + "electrum1.cipig.net:10071", + "electrum2.cipig.net:10071", + "electrum3.cipig.net:10071", + ], + None, + )); println!("{}", json.balance); let rc = block_on(mm.rpc(&json!({ @@ -3516,6 +3764,7 @@ fn test_convert_qrc20_address() { "electrum3.cipig.net:10071", ], "0xba8b71f3544b93e2f681f996da519a98ace0107a", + None, )); // test wallet to contract @@ -3652,7 +3901,7 @@ fn test_validateaddress() { .unwrap(); let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("Log path: {}", mm.log_path.display()); - log!("{:?}", block_on(enable_coins_eth_electrum(&mm, ETH_DEV_NODES))); + log!("{:?}", block_on(enable_coins_eth_electrum(&mm, ETH_DEV_NODES, None))); // test valid RICK address @@ -3933,6 +4182,7 @@ fn qrc20_activate_electrum() { "electrum3.cipig.net:10071", ], "0xba8b71f3544b93e2f681f996da519a98ace0107a", + None, )); assert_eq!( electrum_json["address"].as_str(), @@ -3981,6 +4231,7 @@ fn test_qrc20_withdraw() { "electrum3.cipig.net:10071", ], "0xba8b71f3544b93e2f681f996da519a98ace0107a", + None, )); assert_eq!( electrum_json["address"].as_str(), @@ -4061,6 +4312,7 @@ fn test_qrc20_withdraw_error() { "electrum3.cipig.net:10071", ], "0xba8b71f3544b93e2f681f996da519a98ace0107a", + None, )); let balance = electrum_json["balance"].as_str().unwrap(); assert_eq!(balance, "10"); @@ -4134,11 +4386,17 @@ fn test_get_raw_transaction() { let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("log path: {}", mm.log_path.display()); // RICK - let _electrum = block_on(enable_electrum(&mm, "RICK", false, &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - ])); + let _electrum = block_on(enable_electrum( + &mm, + "RICK", + false, + &[ + "electrum3.cipig.net:10017", + "electrum2.cipig.net:10017", + "electrum1.cipig.net:10017", + ], + None, + )); let raw = block_on(mm.rpc(&json! ({ "mmrpc": "2.0", "userpass": mm.userpass, @@ -4602,7 +4860,10 @@ fn test_buy_conf_settings() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - log!("{:?}", block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES))); + log!( + "{:?}", + block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) + ); log!("Issue bob buy request"); let rc = block_on(mm_bob.rpc(&json! ({ @@ -4671,7 +4932,10 @@ fn test_buy_response_format() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - log!("{:?}", block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES))); + log!( + "{:?}", + block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) + ); log!("Issue bob buy request"); let rc = block_on(mm_bob.rpc(&json! ({ @@ -4718,7 +4982,10 @@ fn test_sell_response_format() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - log!("{:?}", block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES))); + log!( + "{:?}", + block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) + ); log!("Issue bob sell request"); let rc = block_on(mm_bob.rpc(&json! ({ @@ -4765,7 +5032,10 @@ fn test_my_orders_response_format() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - log!("{:?}", block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES))); + log!( + "{:?}", + block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) + ); log!("Issue bob buy request"); let rc = block_on(mm_bob.rpc(&json! ({ @@ -4851,10 +5121,10 @@ fn test_my_orders_after_matched() { let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); // Enable coins on Bob side. Print the replies in case we need the address. - let rc = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES)); + let rc = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)); log!("enable_coins (bob): {:?}", rc); // Enable coins on Alice side. Print the replies in case we need the address. - let rc = block_on(enable_coins_eth_electrum(&mm_alice, ETH_DEV_NODES)); + let rc = block_on(enable_coins_eth_electrum(&mm_alice, ETH_DEV_NODES, None)); log!("enable_coins (alice): {:?}", rc); let rc = block_on(mm_bob.rpc(&json! ({ @@ -4923,7 +5193,10 @@ fn test_sell_conf_settings() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - log!("{:?}", block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES))); + log!( + "{:?}", + block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) + ); log!("Issue bob sell request"); let rc = block_on(mm_bob.rpc(&json! ({ @@ -4993,7 +5266,10 @@ fn test_set_price_conf_settings() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - log!("{:?}", block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES))); + log!( + "{:?}", + block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) + ); log!("Issue bob sell request"); let rc = block_on(mm_bob.rpc(&json! ({ @@ -5418,10 +5694,10 @@ fn test_update_maker_order_after_matched() { let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); // Enable coins on Bob side. Print the replies in case we need the address. - let rc = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES)); + let rc = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)); log!("enable_coins (bob): {:?}", rc); // Enable coins on Alice side. Print the replies in case we need the address. - let rc = block_on(enable_coins_eth_electrum(&mm_alice, ETH_DEV_NODES)); + let rc = block_on(enable_coins_eth_electrum(&mm_alice, ETH_DEV_NODES, None)); log!("enable_coins (alice): {:?}", rc); let rc = block_on(mm_bob.rpc(&json! ({ @@ -5735,7 +6011,10 @@ fn test_sell_min_volume() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - log!("{:?}", block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES))); + log!( + "{:?}", + block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) + ); let min_volume: BigDecimal = "0.1".parse().unwrap(); log!("Issue bob ETH/JST sell request"); @@ -5904,7 +6183,10 @@ fn test_buy_min_volume() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - log!("{:?}", block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES))); + log!( + "{:?}", + block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) + ); let min_volume: BigDecimal = "0.1".parse().unwrap(); log!("Issue bob ETH/JST sell request"); @@ -6011,7 +6293,7 @@ fn test_orderbook_depth() { log!("Bob log path: {}", mm_bob.log_path.display()); // Enable coins on Bob side. Print the replies in case we need the "address". - let bob_coins = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES)); + let bob_coins = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)); log!("enable_coins (bob): {:?}", bob_coins); // issue sell request on Bob side by setting base/rel price log!("Issue bob sell requests"); @@ -6134,11 +6416,17 @@ fn test_get_current_mtp() { let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); let (_dump_log, _dump_dashboard) = mm.mm_dump(); - let electrum = block_on(enable_electrum(&mm, "KMD", false, &[ - "electrum1.cipig.net:10001", - "electrum2.cipig.net:10001", - "electrum3.cipig.net:10001", - ])); + let electrum = block_on(enable_electrum( + &mm, + "KMD", + false, + &[ + "electrum1.cipig.net:10001", + "electrum2.cipig.net:10001", + "electrum3.cipig.net:10001", + ], + None, + )); log!("{:?}", electrum); let rc = block_on(mm.rpc(&json!({ @@ -6237,6 +6525,36 @@ fn test_get_public_key_hash() { assert_eq!(v.result.public_key_hash, "b506088aa2a3b4bb1da3a29bf00ce1a550ea1df9") } +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_get_my_address_hd() { + const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + + let coins = json!([eth_testnet_conf()]); + + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_dump_log, _dump_dashboard) = mm.mm_dump(); + log!("log path: {}", mm.log_path.display()); + + let resp = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "get_my_address", + "params": { + "coin": "ETH", + } + }))) + .unwrap(); + + assert_eq!(resp.0, StatusCode::OK); + let my_wallet_address: Json = json::from_str(&resp.1).unwrap(); + assert_eq!( + my_wallet_address["result"]["wallet_address"], + "0x1737F1FaB40c6Fd3dc729B51C0F97DB3297CCA93" + ) +} + #[test] #[cfg(not(target_arch = "wasm32"))] fn test_get_orderbook_with_same_orderbook_ticker() { @@ -6731,7 +7049,7 @@ fn test_sign_verify_message_eth() { // Enable coins on Bob side. Print the replies in case we need the "address". log!( "enable_coins (bob): {:?}", - block_on(enable_native(&mm_bob, "ETH", ETH_DEV_NODES)) + block_on(enable_native(&mm_bob, "ETH", ETH_DEV_NODES, None)) ); let response = block_on(sign_message(&mm_bob, "ETH")); @@ -6767,8 +7085,8 @@ fn test_no_login() { let (_dump_log, _dump_dashboard) = no_login_node.mm_dump(); log!("log path: {}", no_login_node.log_path.display()); - block_on(enable_electrum_json(&seednode, RICK, false, rick_electrums())); - block_on(enable_electrum_json(&seednode, MORTY, false, morty_electrums())); + block_on(enable_electrum_json(&seednode, RICK, false, rick_electrums(), None)); + block_on(enable_electrum_json(&seednode, MORTY, false, morty_electrums(), None)); let orders = [ // (base, rel, price, volume, min_volume) @@ -7244,7 +7562,7 @@ fn test_tbtc_block_header_sync() { #[test] #[cfg(not(target_arch = "wasm32"))] -fn test_enable_coins_with_hd_account_id() { +fn test_enable_coins_with_enable_hd() { const TX_HISTORY: bool = false; const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; @@ -7256,49 +7574,152 @@ fn test_enable_coins_with_hd_account_id() { btc_segwit_conf(), ]); - let hd_account_id = 0; - let conf_0 = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, hd_account_id, &coins); + let path_to_address = StandardHDCoinAddress { + account: 0, + is_change: false, + address_index: 0, + }; + let conf_0 = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); let mm_hd_0 = MarketMakerIt::start(conf_0.conf, conf_0.rpc_password, None).unwrap(); let (_dump_log, _dump_dashboard) = mm_hd_0.mm_dump(); log!("log path: {}", mm_hd_0.log_path.display()); - let eth = block_on(enable_native(&mm_hd_0, "ETH", ETH_DEV_NODES)); + let eth = block_on(enable_native( + &mm_hd_0, + "ETH", + ETH_DEV_NODES, + Some(path_to_address.clone()), + )); assert_eq!(eth.address, "0x1737F1FaB40c6Fd3dc729B51C0F97DB3297CCA93"); - let jst = block_on(enable_native(&mm_hd_0, "JST", ETH_DEV_NODES)); + let jst = block_on(enable_native( + &mm_hd_0, + "JST", + ETH_DEV_NODES, + Some(path_to_address.clone()), + )); assert_eq!(jst.address, "0x1737F1FaB40c6Fd3dc729B51C0F97DB3297CCA93"); - let rick = block_on(enable_electrum(&mm_hd_0, "RICK", TX_HISTORY, RICK_ELECTRUM_ADDRS)); + let rick = block_on(enable_electrum( + &mm_hd_0, + "RICK", + TX_HISTORY, + RICK_ELECTRUM_ADDRS, + Some(path_to_address.clone()), + )); assert_eq!(rick.address, "RXNtAyDSsY3DS3VxTpJegzoHU9bUX54j56"); let qrc20 = block_on(enable_qrc20( &mm_hd_0, "QRC20", QRC20_ELECTRUMS, "0xd362e096e873eb7907e205fadc6175c6fec7bc44", + Some(path_to_address.clone()), )); assert_eq!(qrc20["address"].as_str(), Some("qRtCTiPHW9e6zH9NcRhjMVfq7sG37SvgrL")); - let btc_segwit = block_on(enable_electrum(&mm_hd_0, "BTC-segwit", TX_HISTORY, RICK_ELECTRUM_ADDRS)); + let btc_segwit = block_on(enable_electrum( + &mm_hd_0, + "BTC-segwit", + TX_HISTORY, + RICK_ELECTRUM_ADDRS, + Some(path_to_address), + )); assert_eq!(btc_segwit.address, "bc1q6vyur5hjul2m0979aadd6u7ptuj9ac4gt0ha0c"); - let hd_account_id = 1; - let conf_1 = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, hd_account_id, &coins); + let path_to_address = StandardHDCoinAddress { + account: 0, + is_change: false, + address_index: 1, + }; + let conf_1 = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); let mm_hd_1 = MarketMakerIt::start(conf_1.conf, conf_1.rpc_password, None).unwrap(); let (_dump_log, _dump_dashboard) = mm_hd_1.mm_dump(); log!("log path: {}", mm_hd_1.log_path.display()); - let eth = block_on(enable_native(&mm_hd_1, "ETH", ETH_DEV_NODES)); + let eth = block_on(enable_native( + &mm_hd_1, + "ETH", + ETH_DEV_NODES, + Some(path_to_address.clone()), + )); assert_eq!(eth.address, "0xDe841899aB4A22E23dB21634e54920aDec402397"); - let jst = block_on(enable_native(&mm_hd_1, "JST", ETH_DEV_NODES)); + let jst = block_on(enable_native( + &mm_hd_1, + "JST", + ETH_DEV_NODES, + Some(path_to_address.clone()), + )); assert_eq!(jst.address, "0xDe841899aB4A22E23dB21634e54920aDec402397"); - let rick = block_on(enable_electrum(&mm_hd_1, "RICK", TX_HISTORY, RICK_ELECTRUM_ADDRS)); + let rick = block_on(enable_electrum( + &mm_hd_1, + "RICK", + TX_HISTORY, + RICK_ELECTRUM_ADDRS, + Some(path_to_address.clone()), + )); assert_eq!(rick.address, "RVyndZp3ZrhGKSwHryyM3Kcz9aq2EJrW1z"); let qrc20 = block_on(enable_qrc20( &mm_hd_1, "QRC20", QRC20_ELECTRUMS, "0xd362e096e873eb7907e205fadc6175c6fec7bc44", + Some(path_to_address.clone()), )); assert_eq!(qrc20["address"].as_str(), Some("qY8FNq2ZDUh52BjNvaroFoeHdr3AAhqsxW")); - let btc_segwit = block_on(enable_electrum(&mm_hd_1, "BTC-segwit", TX_HISTORY, RICK_ELECTRUM_ADDRS)); + let btc_segwit = block_on(enable_electrum( + &mm_hd_1, + "BTC-segwit", + TX_HISTORY, + RICK_ELECTRUM_ADDRS, + Some(path_to_address), + )); assert_eq!(btc_segwit.address, "bc1q6kxcwcrsm5z8pe940xxu294q7588mqvarttxcx"); + + let path_to_address = StandardHDCoinAddress { + account: 77, + is_change: false, + address_index: 7, + }; + let conf_1 = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd_1 = MarketMakerIt::start(conf_1.conf, conf_1.rpc_password, None).unwrap(); + let (_dump_log, _dump_dashboard) = mm_hd_1.mm_dump(); + log!("log path: {}", mm_hd_1.log_path.display()); + + let eth = block_on(enable_native( + &mm_hd_1, + "ETH", + ETH_DEV_NODES, + Some(path_to_address.clone()), + )); + assert_eq!(eth.address, "0xa420a4DBd8C50e6240014Db4587d2ec8D0cE0e6B"); + let jst = block_on(enable_native( + &mm_hd_1, + "JST", + ETH_DEV_NODES, + Some(path_to_address.clone()), + )); + assert_eq!(jst.address, "0xa420a4DBd8C50e6240014Db4587d2ec8D0cE0e6B"); + let rick = block_on(enable_electrum( + &mm_hd_1, + "RICK", + TX_HISTORY, + RICK_ELECTRUM_ADDRS, + Some(path_to_address.clone()), + )); + assert_eq!(rick.address, "RLNu8gszQ8ENUrY3VSyBS2714CNVwn1f7P"); + let qrc20 = block_on(enable_qrc20( + &mm_hd_1, + "QRC20", + QRC20_ELECTRUMS, + "0xd362e096e873eb7907e205fadc6175c6fec7bc44", + Some(path_to_address.clone()), + )); + assert_eq!(qrc20["address"].as_str(), Some("qREuDjyn7dzUPgnCkxPvALz9Szgy7diB5w")); + let btc_segwit = block_on(enable_electrum( + &mm_hd_1, + "BTC-segwit", + TX_HISTORY, + RICK_ELECTRUM_ADDRS, + Some(path_to_address), + )); + assert_eq!(btc_segwit.address, "bc1q0dxnd7afj997a40j86a8a6dq3xs3dwm7rkzams"); } /// `shared_db_id` must be the same for Iguana and all HD accounts derived from the same passphrase. @@ -7311,8 +7732,8 @@ fn test_get_shared_db_id() { let coins = json!([rick_conf()]); let confs = vec![ Mm2TestConf::seednode(PASSPHRASE, &coins), - Mm2TestConf::seednode_with_hd_account(PASSPHRASE, 0, &coins), - Mm2TestConf::seednode_with_hd_account(PASSPHRASE, 1, &coins), + Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins), + Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins), ]; let mut shared_db_id = None; diff --git a/mm2src/mm2_main/tests/mm2_tests/orderbook_sync_tests.rs b/mm2src/mm2_main/tests/mm2_tests/orderbook_sync_tests.rs index 8accb1beba..b221a264c1 100644 --- a/mm2src/mm2_main/tests/mm2_tests/orderbook_sync_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/orderbook_sync_tests.rs @@ -897,16 +897,28 @@ fn orderbook_extended_data() { .unwrap(); let (_dump_log, _dump_dashboard) = &mm.mm_dump(); log!("Log path: {}", mm.log_path.display()); - block_on(enable_electrum(&mm, "RICK", false, &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - ])); - block_on(enable_electrum(&mm, "MORTY", false, &[ - "electrum3.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum1.cipig.net:10018", - ])); + block_on(enable_electrum( + &mm, + "RICK", + false, + &[ + "electrum3.cipig.net:10017", + "electrum2.cipig.net:10017", + "electrum1.cipig.net:10017", + ], + None, + )); + block_on(enable_electrum( + &mm, + "MORTY", + false, + &[ + "electrum3.cipig.net:10018", + "electrum2.cipig.net:10018", + "electrum1.cipig.net:10018", + ], + None, + )); let bob_orders = &[ // (base, rel, price, volume) @@ -1017,16 +1029,28 @@ fn orderbook_should_display_base_rel_volumes() { .unwrap(); let (_dump_log, _dump_dashboard) = &mm.mm_dump(); log!("Log path: {}", mm.log_path.display()); - block_on(enable_electrum(&mm, "RICK", false, &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - ])); - block_on(enable_electrum(&mm, "MORTY", false, &[ - "electrum3.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum1.cipig.net:10018", - ])); + block_on(enable_electrum( + &mm, + "RICK", + false, + &[ + "electrum3.cipig.net:10017", + "electrum2.cipig.net:10017", + "electrum1.cipig.net:10017", + ], + None, + )); + block_on(enable_electrum( + &mm, + "MORTY", + false, + &[ + "electrum3.cipig.net:10018", + "electrum2.cipig.net:10018", + "electrum1.cipig.net:10018", + ], + None, + )); let price = BigRational::new(2.into(), 1.into()); let volume = BigRational::new(1.into(), 1.into()); @@ -1147,7 +1171,7 @@ fn orderbook_should_work_without_coins_activation() { log!( "enable_coins (bob): {:?}", - block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES)) + block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) ); let rc = block_on(mm_bob.rpc(&json!({ @@ -1204,16 +1228,28 @@ fn test_all_orders_per_pair_per_node_must_be_displayed_in_orderbook() { .unwrap(); let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("Log path: {}", mm.log_path.display()); - block_on(enable_electrum(&mm, "RICK", false, &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - ])); - block_on(enable_electrum(&mm, "MORTY", false, &[ - "electrum3.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum1.cipig.net:10018", - ])); + block_on(enable_electrum( + &mm, + "RICK", + false, + &[ + "electrum3.cipig.net:10017", + "electrum2.cipig.net:10017", + "electrum1.cipig.net:10017", + ], + None, + )); + block_on(enable_electrum( + &mm, + "MORTY", + false, + &[ + "electrum3.cipig.net:10018", + "electrum2.cipig.net:10018", + "electrum1.cipig.net:10018", + ], + None, + )); // set 2 orders with different prices let rc = block_on(mm.rpc(&json!({ @@ -1307,11 +1343,11 @@ fn setprice_min_volume_should_be_displayed_in_orderbook() { log!( "enable_coins (bob): {:?}", - block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES)) + block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)) ); log!( "enable_coins (alice): {:?}", - block_on(enable_coins_eth_electrum(&mm_alice, ETH_DEV_NODES)) + block_on(enable_coins_eth_electrum(&mm_alice, ETH_DEV_NODES, None)) ); // issue orderbook call on Alice side to trigger subscription to a topic @@ -1394,12 +1430,13 @@ fn zhtlc_orders_sync_alice_connected_before_creation() { let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); - block_on(enable_electrum_json(&mm_bob, RICK, false, rick_electrums())); + block_on(enable_electrum_json(&mm_bob, RICK, false, rick_electrums(), None)); block_on(enable_z_coin_light( &mm_bob, ZOMBIE_TICKER, ZOMBIE_ELECTRUMS, ZOMBIE_LIGHTWALLETD_URLS, + None, )); let set_price_json = json!({ @@ -1456,12 +1493,13 @@ fn zhtlc_orders_sync_alice_connected_after_creation() { let (_dump_log, _dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - block_on(enable_electrum_json(&mm_bob, "RICK", false, rick_electrums())); + block_on(enable_electrum_json(&mm_bob, "RICK", false, rick_electrums(), None)); block_on(enable_z_coin_light( &mm_bob, ZOMBIE_TICKER, ZOMBIE_ELECTRUMS, ZOMBIE_LIGHTWALLETD_URLS, + None, )); let set_price_json = json!({ @@ -1484,12 +1522,13 @@ fn zhtlc_orders_sync_alice_connected_after_creation() { let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); - block_on(enable_electrum_json(&mm_alice, RICK, false, rick_electrums())); + block_on(enable_electrum_json(&mm_alice, RICK, false, rick_electrums(), None)); block_on(enable_z_coin_light( &mm_alice, ZOMBIE_TICKER, ZOMBIE_ELECTRUMS, ZOMBIE_LIGHTWALLETD_URLS, + None, )); let set_price_json = json!({ diff --git a/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs b/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs index 05e89f74fb..07fad8599f 100644 --- a/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs @@ -1,4 +1,5 @@ use common::block_on; +use crypto::StandardHDCoinAddress; use mm2_number::BigDecimal; use mm2_test_helpers::for_tests::{atom_testnet_conf, disable_coin, disable_coin_err, enable_tendermint, enable_tendermint_token, enable_tendermint_without_balance, @@ -16,6 +17,9 @@ const ATOM_TENDERMINT_RPC_URLS: &[&str] = &["https://rpc.sentry-02.theta-testnet const IRIS_TEST_SEED: &str = "iris test seed"; const IRIS_TESTNET_RPC_URLS: &[&str] = &["http://34.80.202.172:26657"]; +const TENDERMINT_TEST_BIP39_SEED: &str = + "emerge canoe salmon dolphin glow priority random become gasp sell blade argue"; + #[test] fn test_tendermint_activation_and_balance() { let coins = json!([atom_testnet_conf()]); @@ -66,6 +70,27 @@ fn test_tendermint_activation_without_balance() { assert!(result.result.tokens_tickers.unwrap().is_empty()); } +#[test] +fn test_tendermint_hd_address() { + let coins = json!([atom_testnet_conf()]); + // Default address m/44'/118'/0'/0/0 when no path_to_address is specified in activation request + let expected_address = "cosmos1nv4mqaky7n7rqjhch7829kgypx5s8fh62wdtr8"; + + let conf = Mm2TestConf::seednode_with_hd_account(TENDERMINT_TEST_BIP39_SEED, &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let activation_result = block_on(enable_tendermint( + &mm, + ATOM_TICKER, + &[], + ATOM_TENDERMINT_RPC_URLS, + false, + )); + + let result: RpcV2Response = json::from_value(activation_result).unwrap(); + assert_eq!(result.result.address, expected_address); +} + #[test] fn test_tendermint_withdraw() { let coins = json!([atom_testnet_conf()]); @@ -88,6 +113,7 @@ fn test_tendermint_withdraw() { ATOM_TICKER, "cosmos1svaw0aqc4584x825ju7ua03g5xtxwd0ahl86hz", "0.1", + None, )); println!("Withdraw to other {}", json::to_string(&tx_details).unwrap()); // TODO how to check it if the fee is dynamic? @@ -111,6 +137,7 @@ fn test_tendermint_withdraw() { ATOM_TICKER, "cosmos1w5h6wud7a8zpa539rc99ehgl9gwkad3wjsjq8v", "0.1", + None, )); println!("Withdraw to self {}", json::to_string(&tx_details).unwrap()); @@ -137,11 +164,93 @@ fn test_tendermint_withdraw() { ATOM_TICKER, "cosmos1w5h6wud7a8zpa539rc99ehgl9gwkad3wjsjq8v", "0.1", + None, )); let send_raw_tx = block_on(send_raw_transaction(&mm, ATOM_TICKER, &tx_details.tx_hex)); println!("Send raw tx {}", json::to_string(&send_raw_tx).unwrap()); } +#[test] +fn test_tendermint_withdraw_hd() { + let coins = json!([iris_testnet_conf()]); + let coin = coins[0]["coin"].as_str().unwrap(); + + let conf = Mm2TestConf::seednode_with_hd_account(TENDERMINT_TEST_BIP39_SEED, &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let activation_res = block_on(enable_tendermint(&mm, coin, &[], IRIS_TESTNET_RPC_URLS, false)); + println!("Activation with assets {}", json::to_string(&activation_res).unwrap()); + + // We will withdraw from HD account 0 and change 0 and address_index 1 + let path_to_address = StandardHDCoinAddress { + account: 0, + is_change: false, + address_index: 1, + }; + + // just call withdraw without sending to check response correctness + let tx_details = block_on(withdraw_v1( + &mm, + coin, + "iaa1llp0f6qxemgh4g4m5ewk0ew0hxj76avuz8kwd5", + "0.1", + Some(path_to_address.clone()), + )); + println!("Withdraw to other {}", json::to_string(&tx_details).unwrap()); + // TODO how to check it if the fee is dynamic? + /* + let expected_total: BigDecimal = "0.15".parse().unwrap(); + assert_eq!(tx_details.total_amount, expected_total); + assert_eq!(tx_details.spent_by_me, expected_total); + assert_eq!(tx_details.my_balance_change, expected_total * BigDecimal::from(-1)); + */ + assert_eq!(tx_details.received_by_me, BigDecimal::default()); + assert_eq!(tx_details.to, vec![ + "iaa1llp0f6qxemgh4g4m5ewk0ew0hxj76avuz8kwd5".to_owned() + ]); + assert_eq!(tx_details.from, vec![ + "iaa1tpd0um0r3z0y88p3gkv3y38dq8lmqc2xs9u0pv".to_owned() + ]); + + // withdraw and send transaction to ourselves + let tx_details = block_on(withdraw_v1( + &mm, + coin, + "iaa1tpd0um0r3z0y88p3gkv3y38dq8lmqc2xs9u0pv", + "0.1", + Some(path_to_address.clone()), + )); + println!("Withdraw to self {}", json::to_string(&tx_details).unwrap()); + + // TODO how to check it if the fee is dynamic? + /* + let expected_total: BigDecimal = "0.15".parse().unwrap(); + let expected_balance_change: BigDecimal = "-0.05".parse().unwrap(); + assert_eq!(tx_details.total_amount, expected_total); + assert_eq!(tx_details.spent_by_me, expected_total); + assert_eq!(tx_details.my_balance_change, expected_balance_change); + */ + let expected_received: BigDecimal = "0.1".parse().unwrap(); + assert_eq!(tx_details.received_by_me, expected_received); + + assert_eq!(tx_details.to, vec![ + "iaa1tpd0um0r3z0y88p3gkv3y38dq8lmqc2xs9u0pv".to_owned() + ]); + assert_eq!(tx_details.from, vec![ + "iaa1tpd0um0r3z0y88p3gkv3y38dq8lmqc2xs9u0pv".to_owned() + ]); + + let tx_details = block_on(withdraw_v1( + &mm, + coin, + "iaa1tpd0um0r3z0y88p3gkv3y38dq8lmqc2xs9u0pv", + "0.1", + Some(path_to_address), + )); + let send_raw_tx = block_on(send_raw_transaction(&mm, coin, &tx_details.tx_hex)); + println!("Send raw tx {}", json::to_string(&send_raw_tx).unwrap()); +} + #[test] fn test_custom_gas_limit_on_tendermint_withdraw() { let coins = json!([atom_testnet_conf()]); @@ -198,7 +307,14 @@ fn test_tendermint_ibc_withdraw() { let activation_res = block_on(enable_tendermint_token(&mm, token)); println!("Token activation {}", json::to_string(&activation_res).unwrap()); - let tx_details = block_on(ibc_withdraw(&mm, IBC_SOURCE_CHANNEL, token, IBC_TARGET_ADDRESS, "0.1")); + let tx_details = block_on(ibc_withdraw( + &mm, + IBC_SOURCE_CHANNEL, + token, + IBC_TARGET_ADDRESS, + "0.1", + None, + )); println!("IBC transfer to atom address {}", json::to_string(&tx_details).unwrap()); let expected_spent: BigDecimal = "0.1".parse().unwrap(); @@ -207,10 +323,65 @@ fn test_tendermint_ibc_withdraw() { assert_eq!(tx_details.to, vec![IBC_TARGET_ADDRESS.to_owned()]); assert_eq!(tx_details.from, vec![MY_ADDRESS.to_owned()]); - let tx_details = block_on(ibc_withdraw(&mm, IBC_SOURCE_CHANNEL, token, IBC_TARGET_ADDRESS, "0.1")); + let tx_details = block_on(ibc_withdraw( + &mm, + IBC_SOURCE_CHANNEL, + token, + IBC_TARGET_ADDRESS, + "0.1", + None, + )); let send_raw_tx = block_on(send_raw_transaction(&mm, token, &tx_details.tx_hex)); println!("Send raw tx {}", json::to_string(&send_raw_tx).unwrap()); } +#[test] +fn test_tendermint_ibc_withdraw_hd() { + // visit `{rpc_url}/ibc/core/channel/v1/channels?pagination.limit=10000` to see the full list of ibc channels + const IBC_SOURCE_CHANNEL: &str = "channel-93"; + + const IBC_TARGET_ADDRESS: &str = "cosmos1r5v5srda7xfth3hn2s26txvrcrntldjumt8mhl"; + const MY_ADDRESS: &str = "iaa1tpd0um0r3z0y88p3gkv3y38dq8lmqc2xs9u0pv"; + + let coins = json!([iris_testnet_conf()]); + let coin = coins[0]["coin"].as_str().unwrap(); + + let conf = Mm2TestConf::seednode_with_hd_account(TENDERMINT_TEST_BIP39_SEED, &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let activation_res = block_on(enable_tendermint(&mm, coin, &[], IRIS_TESTNET_RPC_URLS, false)); + println!("Activation with assets {}", json::to_string(&activation_res).unwrap()); + + // We will withdraw from HD account 0 and change 0 and address_index 1 + let path_to_address = StandardHDCoinAddress { + account: 0, + is_change: false, + address_index: 1, + }; + + let tx_details = block_on(ibc_withdraw( + &mm, + IBC_SOURCE_CHANNEL, + coin, + IBC_TARGET_ADDRESS, + "0.1", + Some(path_to_address.clone()), + )); + println!("IBC transfer to atom address {}", json::to_string(&tx_details).unwrap()); + + assert_eq!(tx_details.to, vec![IBC_TARGET_ADDRESS.to_owned()]); + assert_eq!(tx_details.from, vec![MY_ADDRESS.to_owned()]); + + let tx_details = block_on(ibc_withdraw( + &mm, + IBC_SOURCE_CHANNEL, + coin, + IBC_TARGET_ADDRESS, + "0.1", + Some(path_to_address), + )); + let send_raw_tx = block_on(send_raw_transaction(&mm, coin, &tx_details.tx_hex)); + println!("Send raw tx {}", json::to_string(&send_raw_tx).unwrap()); +} #[test] fn test_tendermint_token_activation_and_withdraw() { @@ -233,6 +404,7 @@ fn test_tendermint_token_activation_and_withdraw() { token, "iaa1llp0f6qxemgh4g4m5ewk0ew0hxj76avuz8kwd5", "0.1", + None, )); println!("Withdraw to other {}", json::to_string(&tx_details).unwrap()); @@ -263,6 +435,7 @@ fn test_tendermint_token_activation_and_withdraw() { token, "iaa1e0rx87mdj79zejewuc4jg7ql9ud2286g2us8f2", "0.1", + None, )); println!("Withdraw to self {}", json::to_string(&tx_details).unwrap()); @@ -292,6 +465,7 @@ fn test_tendermint_token_activation_and_withdraw() { token, "iaa1e0rx87mdj79zejewuc4jg7ql9ud2286g2us8f2", "0.1", + None, )); let send_raw_tx = block_on(send_raw_transaction(&mm, token, &tx_details.tx_hex)); println!("Send raw tx {}", json::to_string(&send_raw_tx).unwrap()); diff --git a/mm2src/mm2_main/tests/mm2_tests/z_coin_tests.rs b/mm2src/mm2_main/tests/mm2_tests/z_coin_tests.rs index c2884dd875..3c7836108a 100644 --- a/mm2src/mm2_main/tests/mm2_tests/z_coin_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/z_coin_tests.rs @@ -60,6 +60,7 @@ fn activate_z_coin_light() { ZOMBIE_TICKER, ZOMBIE_ELECTRUMS, ZOMBIE_LIGHTWALLETD_URLS, + None, )); let balance = match activation_result.wallet_balance { @@ -74,8 +75,7 @@ fn activate_z_coin_light() { fn activate_z_coin_with_hd_account() { let coins = json!([zombie_conf()]); - let hd_account_id = 0; - let conf = Mm2TestConf::seednode_with_hd_account(ZOMBIE_TEST_BIP39_ACTIVATION_SEED, hd_account_id, &coins); + let conf = Mm2TestConf::seednode_with_hd_account(ZOMBIE_TEST_BIP39_ACTIVATION_SEED, &coins); let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); let activation_result = block_on(enable_z_coin_light( @@ -83,6 +83,7 @@ fn activate_z_coin_with_hd_account() { ZOMBIE_TICKER, ZOMBIE_ELECTRUMS, ZOMBIE_LIGHTWALLETD_URLS, + Some(0), )); let actual = match activation_result.wallet_balance { @@ -109,6 +110,7 @@ fn test_z_coin_tx_history() { ZOMBIE_TICKER, ZOMBIE_ELECTRUMS, ZOMBIE_LIGHTWALLETD_URLS, + None, )); let tx_history = block_on(z_coin_tx_history(&mm, ZOMBIE_TICKER, 5, None)); @@ -352,6 +354,7 @@ fn withdraw_z_coin_light() { ZOMBIE_TICKER, ZOMBIE_ELECTRUMS, ZOMBIE_LIGHTWALLETD_URLS, + None, )); println!("{:?}", activation_result); @@ -393,11 +396,12 @@ fn trade_rick_zombie_light() { ZOMBIE_TICKER, ZOMBIE_ELECTRUMS, ZOMBIE_LIGHTWALLETD_URLS, + None, )); println!("Bob ZOMBIE activation {:?}", zombie_activation); - let rick_activation = block_on(enable_electrum_json(&mm_bob, RICK, false, rick_electrums())); + let rick_activation = block_on(enable_electrum_json(&mm_bob, RICK, false, rick_electrums(), None)); println!("Bob RICK activation {:?}", rick_activation); @@ -425,11 +429,12 @@ fn trade_rick_zombie_light() { ZOMBIE_TICKER, ZOMBIE_ELECTRUMS, ZOMBIE_LIGHTWALLETD_URLS, + None, )); println!("Alice ZOMBIE activation {:?}", zombie_activation); - let rick_activation = block_on(enable_electrum_json(&mm_alice, RICK, false, rick_electrums())); + let rick_activation = block_on(enable_electrum_json(&mm_alice, RICK, false, rick_electrums(), None)); println!("Alice RICK activation {:?}", rick_activation); @@ -481,6 +486,7 @@ fn activate_pirate_light() { ARRR, PIRATE_ELECTRUMS, PIRATE_LIGHTWALLETD_URLS, + None, )); let balance = match activation_result.wallet_balance { diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 4004cde892..8376afa8a8 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -7,7 +7,7 @@ use common::executor::Timer; use common::log::debug; use common::{cfg_native, now_float, now_ms, repeatable, wait_until_ms, PagingOptionsEnum}; use common::{get_utc_timestamp, log}; -use crypto::CryptoCtx; +use crypto::{CryptoCtx, StandardHDCoinAddress}; use gstuff::{try_s, ERR, ERRL}; use http::{HeaderMap, StatusCode}; use lazy_static::lazy_static; @@ -191,7 +191,7 @@ impl Mm2TestConf { } } - pub fn seednode_with_hd_account(passphrase: &str, hd_account_id: u32, coins: &Json) -> Self { + pub fn seednode_with_hd_account(passphrase: &str, coins: &Json) -> Self { Mm2TestConf { conf: json!({ "gui": "nogui", @@ -200,7 +200,7 @@ impl Mm2TestConf { "coins": coins, "rpc_password": DEFAULT_RPC_PASSWORD, "i_am_seed": true, - "hd_account_id": hd_account_id, + "enable_hd": true, }), rpc_password: DEFAULT_RPC_PASSWORD.into(), } @@ -236,7 +236,7 @@ impl Mm2TestConf { } } - pub fn light_node_with_hd_account(passphrase: &str, hd_account_id: u32, coins: &Json, seednodes: &[&str]) -> Self { + pub fn light_node_with_hd_account(passphrase: &str, coins: &Json, seednodes: &[&str]) -> Self { Mm2TestConf { conf: json!({ "gui": "nogui", @@ -245,7 +245,7 @@ impl Mm2TestConf { "coins": coins, "rpc_password": DEFAULT_RPC_PASSWORD, "seednodes": seednodes, - "hd_account_id": hd_account_id, + "enable_hd": true, }), rpc_password: DEFAULT_RPC_PASSWORD.into(), } @@ -275,26 +275,26 @@ impl Mm2TestConfForSwap { const ALICE_HD_PASSPHRASE: &'static str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; - pub fn bob_conf_with_policy(priv_key_policy: Mm2InitPrivKeyPolicy, coins: &Json) -> Mm2TestConf { + pub fn bob_conf_with_policy(priv_key_policy: &Mm2InitPrivKeyPolicy, coins: &Json) -> Mm2TestConf { match priv_key_policy { Mm2InitPrivKeyPolicy::Iguana => { let bob_passphrase = crate::get_passphrase!(".env.seed", "BOB_PASSPHRASE").unwrap(); Mm2TestConf::seednode(&bob_passphrase, coins) }, - Mm2InitPrivKeyPolicy::GlobalHDAccount(hd_account_id) => { - Mm2TestConf::seednode_with_hd_account(Self::BOB_HD_PASSPHRASE, hd_account_id, coins) + Mm2InitPrivKeyPolicy::GlobalHDAccount => { + Mm2TestConf::seednode_with_hd_account(Self::BOB_HD_PASSPHRASE, coins) }, } } - pub fn alice_conf_with_policy(priv_key_policy: Mm2InitPrivKeyPolicy, coins: &Json, bob_ip: &str) -> Mm2TestConf { + pub fn alice_conf_with_policy(priv_key_policy: &Mm2InitPrivKeyPolicy, coins: &Json, bob_ip: &str) -> Mm2TestConf { match priv_key_policy { Mm2InitPrivKeyPolicy::Iguana => { let alice_passphrase = crate::get_passphrase!(".env.client", "ALICE_PASSPHRASE").unwrap(); Mm2TestConf::light_node(&alice_passphrase, coins, &[bob_ip]) }, - Mm2InitPrivKeyPolicy::GlobalHDAccount(hd_account_id) => { - Mm2TestConf::light_node_with_hd_account(Self::ALICE_HD_PASSPHRASE, hd_account_id, coins, &[bob_ip]) + Mm2InitPrivKeyPolicy::GlobalHDAccount => { + Mm2TestConf::light_node_with_hd_account(Self::ALICE_HD_PASSPHRASE, coins, &[bob_ip]) }, } } @@ -302,7 +302,7 @@ impl Mm2TestConfForSwap { pub enum Mm2InitPrivKeyPolicy { Iguana, - GlobalHDAccount(u32), + GlobalHDAccount, } pub fn zombie_conf() -> Json { @@ -455,7 +455,8 @@ pub fn atom_testnet_conf() -> Json { "account_prefix": "cosmos", "chain_id": "theta-testnet-001", }, - } + }, + "derivation_path": "m/44'/118'", }) } @@ -579,6 +580,7 @@ pub fn tbtc_segwit_conf() -> Json { "txfee": 0, "estimate_fee_mode": "ECONOMICAL", "required_confirmations": 0, + "derivation_path": "m/84'/1'", "address_format": { "format": "segwit" }, @@ -625,6 +627,7 @@ pub fn eth_testnet_conf() -> Json { json!({ "coin": "ETH", "name": "ethereum", + "mm2": 1, "derivation_path": "m/44'/60'", "protocol": { "type": "ETH" @@ -1466,14 +1469,26 @@ pub fn mm_spat() -> (&'static str, MarketMakerIt, RaiiDump, RaiiDump) { /// Asks MM to enable the given currency in electrum mode /// fresh list of servers at https://github.com/jl777/coins/blob/master/electrums/. -pub async fn enable_electrum(mm: &MarketMakerIt, coin: &str, tx_history: bool, urls: &[&str]) -> Json { +pub async fn enable_electrum( + mm: &MarketMakerIt, + coin: &str, + tx_history: bool, + urls: &[&str], + path_to_address: Option, +) -> Json { let servers = urls.iter().map(|url| json!({ "url": url })).collect(); - enable_electrum_json(mm, coin, tx_history, servers).await + enable_electrum_json(mm, coin, tx_history, servers, path_to_address).await } /// Asks MM to enable the given currency in electrum mode /// fresh list of servers at https://github.com/jl777/coins/blob/master/electrums/. -pub async fn enable_electrum_json(mm: &MarketMakerIt, coin: &str, tx_history: bool, servers: Vec) -> Json { +pub async fn enable_electrum_json( + mm: &MarketMakerIt, + coin: &str, + tx_history: bool, + servers: Vec, + path_to_address: Option, +) -> Json { let electrum = mm .rpc(&json!({ "userpass": mm.userpass, @@ -1482,6 +1497,7 @@ pub async fn enable_electrum_json(mm: &MarketMakerIt, coin: &str, tx_history: bo "servers": servers, "mm2": 1, "tx_history": tx_history, + "path_to_address": path_to_address.unwrap_or_default(), })) .await .unwrap(); @@ -1495,7 +1511,13 @@ pub async fn enable_electrum_json(mm: &MarketMakerIt, coin: &str, tx_history: bo json::from_str(&electrum.1).unwrap() } -pub async fn enable_qrc20(mm: &MarketMakerIt, coin: &str, urls: &[&str], swap_contract_address: &str) -> Json { +pub async fn enable_qrc20( + mm: &MarketMakerIt, + coin: &str, + urls: &[&str], + swap_contract_address: &str, + path_to_address: Option, +) -> Json { let servers: Vec<_> = urls.iter().map(|url| json!({ "url": url })).collect(); let electrum = mm .rpc(&json!({ @@ -1505,6 +1527,7 @@ pub async fn enable_qrc20(mm: &MarketMakerIt, coin: &str, urls: &[&str], swap_co "servers": servers, "mm2": 1, "swap_contract_address": swap_contract_address, + "path_to_address": path_to_address.unwrap_or_default(), })) .await .unwrap(); @@ -1573,7 +1596,12 @@ pub fn get_passphrase(path: &dyn AsRef, env: &str) -> Result Json { +pub async fn enable_native( + mm: &MarketMakerIt, + coin: &str, + urls: &[&str], + path_to_address: Option, +) -> Json { let native = mm .rpc(&json!({ "userpass": mm.userpass, @@ -1582,6 +1610,7 @@ pub async fn enable_native(mm: &MarketMakerIt, coin: &str, urls: &[&str]) -> Jso "urls": urls, // Dev chain swap contract address "swap_contract_address": ETH_DEV_SWAP_CONTRACT, + "path_to_address": path_to_address.unwrap_or_default(), "mm2": 1, })) .await @@ -1697,6 +1726,7 @@ pub async fn enable_bch_with_tokens( tokens: &[&str], mode: UtxoRpcMode, tx_history: bool, + path_to_address: Option, ) -> Json { let slp_requests: Vec<_> = tokens.iter().map(|ticker| json!({ "ticker": ticker })).collect(); @@ -1712,6 +1742,7 @@ pub async fn enable_bch_with_tokens( "mode": mode, "tx_history": tx_history, "slp_tokens_requests": slp_requests, + "path_to_address": path_to_address.unwrap_or_default(), } })) .await @@ -2200,7 +2231,13 @@ pub async fn init_withdraw(mm: &MarketMakerIt, coin: &str, to: &str, amount: &st json::from_str(&request.1).unwrap() } -pub async fn withdraw_v1(mm: &MarketMakerIt, coin: &str, to: &str, amount: &str) -> TransactionDetails { +pub async fn withdraw_v1( + mm: &MarketMakerIt, + coin: &str, + to: &str, + amount: &str, + from: Option, +) -> TransactionDetails { let request = mm .rpc(&json!({ "userpass": mm.userpass, @@ -2208,6 +2245,7 @@ pub async fn withdraw_v1(mm: &MarketMakerIt, coin: &str, to: &str, amount: &str) "coin": coin, "to": to, "amount": amount, + "from": from, })) .await .unwrap(); @@ -2221,6 +2259,7 @@ pub async fn ibc_withdraw( coin: &str, to: &str, amount: &str, + from: Option, ) -> TransactionDetails { let request = mm .rpc(&json!({ @@ -2231,7 +2270,8 @@ pub async fn ibc_withdraw( "ibc_source_channel": source_channel, "coin": coin, "to": to, - "amount": amount + "amount": amount, + "from": from, } })) .await @@ -2289,7 +2329,13 @@ pub async fn init_z_coin_native(mm: &MarketMakerIt, coin: &str) -> Json { json::from_str(&request.1).unwrap() } -pub async fn init_z_coin_light(mm: &MarketMakerIt, coin: &str, electrums: &[&str], lightwalletd_urls: &[&str]) -> Json { +pub async fn init_z_coin_light( + mm: &MarketMakerIt, + coin: &str, + electrums: &[&str], + lightwalletd_urls: &[&str], + account: Option, +) -> Json { let request = mm .rpc(&json!({ "userpass": mm.userpass, @@ -2304,7 +2350,8 @@ pub async fn init_z_coin_light(mm: &MarketMakerIt, coin: &str, electrums: &[&str "electrum_servers": electrum_servers_rpc(electrums), "light_wallet_d_servers": lightwalletd_urls, }, - } + }, + "account": account.unwrap_or_default(), }, } }))