diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index a4763b385c..b9fc1ee0a5 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -839,6 +839,10 @@ impl MarketCoinOps for EthCoin { let addr = try_s!(addr_from_raw_pubkey(&pubkey_bytes)); Ok(format!("{:#02x}", addr)) } + + fn display_priv_key(&self) -> String { + format!("{:#02x}", self.key_pair.secret()) + } } pub fn signed_eth_tx_from_bytes(bytes: &[u8]) -> Result { diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 5f0df6e5c9..bd7d7fb318 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -233,6 +233,8 @@ pub trait MarketCoinOps { fn current_block(&self) -> Box + Send>; fn address_from_pubkey_str(&self, pubkey: &str) -> Result; + + fn display_priv_key(&self) -> String; } #[derive(Deserialize)] @@ -677,3 +679,19 @@ pub async fn set_required_confirmations(ctx: MmArc, req: Json) -> Result Result>, String> { + let ticker = try_s!(req["coin"].as_str().ok_or ("No 'coin' field")).to_owned(); + let coin = match lp_coinfindᵃ(&ctx, &ticker).await { + Ok(Some(t)) => t, + Ok(None) => return ERR!("No such coin: {}", ticker), + Err(err) => return ERR!("!lp_coinfind({}): {}", ticker, err), + }; + let res = try_s!(json::to_vec(&json!({ + "result": { + "coin": ticker, + "priv_key": coin.display_priv_key(), + } + }))); + Ok(try_s!(Response::builder().body(res))) +} diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 551724cf10..da9dccd028 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -56,6 +56,10 @@ impl MarketCoinOps for TestCoin { fn address_from_pubkey_str(&self, pubkey: &str) -> Result { unimplemented!() } + + fn display_priv_key(&self) -> String { + unimplemented!() + } } #[mockable] diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index 40ff263355..a7feaa2b8d 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -1350,6 +1350,10 @@ impl MarketCoinOps for UtxoCoin { let addr = try_s!(address_from_raw_pubkey(&pubkey_bytes, self.pub_addr_prefix, self.pub_t_addr_prefix, self.checksum_type)); Ok(addr.to_string()) } + + fn display_priv_key(&self) -> String { + format!("{}", self.key_pair.private()) + } } async fn withdraw_impl(coin: UtxoCoin, req: WithdrawRequest) -> Result { diff --git a/mm2src/mm2_tests.rs b/mm2src/mm2_tests.rs index 1a900b7477..1b78f83d9d 100644 --- a/mm2src/mm2_tests.rs +++ b/mm2src/mm2_tests.rs @@ -1924,6 +1924,52 @@ fn orderbook_should_display_rational_amounts() { assert_eq!(volume, volume_in_orderbook); } +fn check_priv_key(mm: &MarketMakerIt, coin: &str, expected_priv_key: &str) { + let rc = unwrap! (block_on (mm.rpc (json! ({ + "userpass": mm.userpass, + "method": "show_priv_key", + "coin": coin + })))); + assert!(rc.0.is_success(), "!show_priv_key: {}", rc.1); + let privkey: Json = unwrap!(json::from_str(&rc.1)); + assert_eq!(privkey["result"]["priv_key"], Json::from(expected_priv_key)) +} + +#[test] +#[cfg(feature = "native")] +fn test_show_priv_key() { + let coins = json! ([ + {"coin":"RICK","asset":"RICK","rpcport":8923,"txversion":4,"overwintered":1}, + {"coin":"MORTY","asset":"MORTY","rpcport":11608,"txversion":4,"overwintered":1}, + {"coin":"ETH","name":"ethereum","etomic":"0x0000000000000000000000000000000000000000"}, + {"coin":"JST","name":"jst","etomic":"0x2b294F029Fde858b2c62184e8390591755521d8E"} + ]); + + let mut mm = unwrap!(MarketMakerIt::start ( + json! ({ + "gui": "nogui", + "netid": 9998, + "myipaddr": env::var ("BOB_TRADE_IP") .ok(), + "rpcip": env::var ("BOB_TRADE_IP") .ok(), + "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| unwrap! (s.parse::())), + "passphrase": "bob passphrase", + "coins": coins, + "rpc_password": "pass", + "i_am_seed": true, + }), + "pass".into(), + match var ("LOCAL_THREAD_MM") {Ok (ref e) if e == "bob" => Some (local_start()), _ => None} + )); + + let (_dump_log, _dump_dashboard) = mm_dump (&mm.log_path); + log!({"Log path: {}", mm.log_path.display()}); + unwrap! (block_on (mm.wait_for_log (22., |log| log.contains (">>>>>>>>> DEX stats ")))); + log! ({"enable_coins: {:?}", block_on (enable_coins_eth_electrum (&mm, vec!["http://195.201.0.6:8565"]))}); + + check_priv_key(&mm, "RICK", "UvCjJf4dKSs2vFGVtCnUTAhR5FTZGdg43DDRa9s7s5DV1sSDX14g"); + check_priv_key(&mm, "ETH", "0xb8c774f071de08c7fd8f62b97f1a5726f6ce9f1bcf141b70b86689254ed6714e"); +} + // HOWTO // 1. Install Firefox. // 2. Install forked version of wasm-bindgen-cli: cargo install wasm-bindgen-cli --git https://github.com/artemii235/wasm-bindgen.git diff --git a/mm2src/rpc.rs b/mm2src/rpc.rs index 0d7e450b6a..31b53cf8ea 100644 --- a/mm2src/rpc.rs +++ b/mm2src/rpc.rs @@ -21,7 +21,8 @@ #![cfg_attr(not(feature = "native"), allow(dead_code))] use bytes::Bytes; -use coins::{get_enabled_coins, get_trade_fee, send_raw_transaction, set_required_confirmations, withdraw, my_tx_history}; +use coins::{get_enabled_coins, get_trade_fee, my_tx_history, send_raw_transaction, set_required_confirmations, + show_priv_key, withdraw}; use common::{err_to_rpc_json_string, HyRes}; #[cfg(feature = "native")] use common::wio::{slurp_reqʰ, CORE, CPUPOOL, HTTP}; @@ -61,8 +62,6 @@ pub mod lp_signatures; /// Lists the RPC method not requiring the "userpass" authentication. /// None is also public to skip auth and display proper error in case of method is missing const PUBLIC_METHODS: &[Option<&str>] = &[ // Sorted alphanumerically (on the first letter) for readability. - Some("balance"), - Some("balances"), Some("fundvalue"), Some("getprice"), Some("getpeers"), @@ -205,8 +204,8 @@ pub fn dispatcher (req: Json, ctx: MmArc) -> DispatcherRes { "cancel_order" => cancel_order (ctx, req), "coins_needed_for_kick_start" => hyres (coins_needed_for_kick_start (ctx)), "disable_coin" => disable_coin(ctx, req), - "enable" => hyres (enable (ctx, req)), "electrum" => hyres (electrum (ctx, req)), + "enable" => hyres (enable (ctx, req)), "get_enabled_coins" => hyres (get_enabled_coins (ctx)), "get_trade_fee" => hyres(get_trade_fee (ctx, req)), // "fundvalue" => lp_fundvalue (ctx, req, false), @@ -218,27 +217,28 @@ pub fn dispatcher (req: Json, ctx: MmArc) -> DispatcherRes { #[cfg(not(feature = "native"))] {return DispatcherRes::NoMatch (req)} }, // "inventory" => inventory (ctx, req), - "my_orders" => my_orders (ctx), "my_balance" => my_balance (ctx, req), + "my_orders" => my_orders (ctx), + "my_recent_swaps" => my_recent_swaps(ctx, req), + "my_swap_status" => my_swap_status(ctx, req), "my_tx_history" => my_tx_history(ctx, req), "notify" => lp_signatures::lp_notify_recv (ctx, req), // Invoked usually from the `lp_command_q_loop` - "orderbook" => hyres(orderbook(ctx, req)), "order_status" => order_status (ctx, req), - // "passphrase" => passphrase (ctx, req), - "sell" => hyres(sell(ctx, req)), - "send_raw_transaction" => hyres (send_raw_transaction (ctx, req)), - "setprice" => hyres(set_price (ctx, req)), - "stop" => stop (ctx), - "my_recent_swaps" => my_recent_swaps(ctx, req), - "my_swap_status" => my_swap_status(ctx, req), + "orderbook" => hyres(orderbook(ctx, req)), "recover_funds_of_swap" => { #[cfg(feature = "native")] { Box::new(CPUPOOL.spawn_fn(move || { hyres(recover_funds_of_swap (ctx, req)) })) } #[cfg(not(feature = "native"))] {return DispatcherRes::NoMatch (req)} }, + // "passphrase" => passphrase (ctx, req), + "sell" => hyres(sell(ctx, req)), + "show_priv_key" => hyres(show_priv_key(ctx, req)), + "send_raw_transaction" => hyres (send_raw_transaction (ctx, req)), "set_required_confirmations" => hyres(set_required_confirmations(ctx, req)), + "setprice" => hyres(set_price (ctx, req)), "stats_swap_status" => stats_swap_status(ctx, req), + "stop" => stop (ctx), "version" => version(), "withdraw" => hyres (withdraw (ctx, req)), _ => return DispatcherRes::NoMatch (req)