From ffc9d5e07f62b9977d667c2347c7da8ce446a1ca Mon Sep 17 00:00:00 2001 From: shamardy Date: Mon, 2 Aug 2021 14:53:28 +0200 Subject: [PATCH 1/3] fix withdraw to p2sh --- mm2src/coins/utxo.rs | 12 ++++++-- mm2src/coins/utxo/rpc_clients.rs | 4 +-- mm2src/coins/utxo/utxo_common.rs | 19 +++++++++---- mm2src/coins/utxo/utxo_tests.rs | 49 +++++++++++++++++++++++++++++++- 4 files changed, 72 insertions(+), 12 deletions(-) diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index d77a2614c7..9073c3e72d 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -1197,7 +1197,7 @@ pub trait UtxoCoinBuilder { hrp: conf.bech32_hrp.clone(), addr_format, }; - let my_script_pubkey = output_script(&my_address).to_bytes(); + let my_script_pubkey = output_script(&my_address, true).to_bytes(); let rpc_client = try_s!(self.rpc_client().await); let tx_fee = try_s!(self.tx_fee(&rpc_client).await); let decimals = try_s!(self.decimals(&rpc_client).await); @@ -1990,10 +1990,16 @@ fn script_sig(message: &H256, key_pair: &KeyPair, fork_id: u32) -> Result Script { +pub fn output_script(address: &Address, is_p2pkh: bool) -> Script { match address.addr_format { UtxoAddressFormat::Segwit => Builder::build_p2wpkh(&address.hash), - _ => Builder::build_p2pkh(&address.hash), + _ => { + if is_p2pkh { + Builder::build_p2pkh(&address.hash) + } else { + Builder::build_p2sh(&address.hash) + } + }, } } diff --git a/mm2src/coins/utxo/rpc_clients.rs b/mm2src/coins/utxo/rpc_clients.rs index edc3dc6e9e..52e3889eba 100644 --- a/mm2src/coins/utxo/rpc_clients.rs +++ b/mm2src/coins/utxo/rpc_clients.rs @@ -1478,7 +1478,7 @@ impl ElectrumClient { #[cfg_attr(test, mockable)] impl UtxoRpcClientOps for ElectrumClient { fn list_unspent(&self, address: &Address, _decimals: u8) -> UtxoRpcFut> { - let script = output_script(address); + let script = output_script(address, true); let script_hash = electrum_script_hash(&script); Box::new( self.scripthash_list_unspent(&hex::encode(script_hash)) @@ -1538,7 +1538,7 @@ impl UtxoRpcClientOps for ElectrumClient { } fn display_balance(&self, address: Address, decimals: u8) -> RpcRes { - let hash = electrum_script_hash(&output_script(&address)); + let hash = electrum_script_hash(&output_script(&address, true)); let hash_str = hex::encode(hash); Box::new(self.scripthash_get_balance(&hash_str).map(move |result| { BigDecimal::from(result.confirmed + result.unconfirmed) / BigDecimal::from(10u64.pow(decimals as u32)) diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 482551094d..87f49a0794 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -290,7 +290,7 @@ where let dust: u64 = coin.as_ref().dust_amount; let lock_time = (now_ms() / 1000) as u32; - let change_script_pubkey = output_script(&coin.as_ref().my_address).to_bytes(); + let change_script_pubkey = output_script(&coin.as_ref().my_address, true).to_bytes(); let coin_tx_fee = match fee { Some(f) => f, None => coin.get_tx_fee().await?, @@ -738,7 +738,7 @@ where ); let fut = async move { let fee = try_s!(coin.get_htlc_spend_fee().await); - let script_pubkey = output_script(&coin.as_ref().my_address).to_bytes(); + let script_pubkey = output_script(&coin.as_ref().my_address, true).to_bytes(); let output = TransactionOutput { value: prev_tx.outputs[0].value - fee, script_pubkey, @@ -785,7 +785,7 @@ where ); let fut = async move { let fee = try_s!(coin.get_htlc_spend_fee().await); - let script_pubkey = output_script(&coin.as_ref().my_address).to_bytes(); + let script_pubkey = output_script(&coin.as_ref().my_address, true).to_bytes(); let output = TransactionOutput { value: prev_tx.outputs[0].value - fee, script_pubkey, @@ -829,7 +829,7 @@ where ); let fut = async move { let fee = try_s!(coin.get_htlc_spend_fee().await); - let script_pubkey = output_script(&coin.as_ref().my_address).to_bytes(); + let script_pubkey = output_script(&coin.as_ref().my_address, true).to_bytes(); let output = TransactionOutput { value: prev_tx.outputs[0].value - fee, script_pubkey, @@ -873,7 +873,7 @@ where ); let fut = async move { let fee = try_s!(coin.get_htlc_spend_fee().await); - let script_pubkey = output_script(&coin.as_ref().my_address).to_bytes(); + let script_pubkey = output_script(&coin.as_ref().my_address, true).to_bytes(); let output = TransactionOutput { value: prev_tx.outputs[0].value - fee, script_pubkey, @@ -1378,7 +1378,14 @@ where .address_from_str(&req.to) .map_to_mm(WithdrawError::InvalidAddress)?; - let script_pubkey = output_script(&to).to_bytes(); + let is_p2pkh = to.prefix == conf.pub_addr_prefix && to.t_addr_prefix == conf.pub_t_addr_prefix; + let is_p2sh = to.prefix == conf.p2sh_addr_prefix && to.t_addr_prefix == conf.p2sh_t_addr_prefix && conf.segwit; + + if !is_p2pkh && !is_p2sh { + return MmError::err(WithdrawError::InvalidAddress("Expected either P2PKH or P2SH".into())); + } + + let script_pubkey = output_script(&to, is_p2pkh).to_bytes(); let signature_version = match coin.as_ref().my_address.addr_format { UtxoAddressFormat::Segwit => SignatureVersion::WitnessV0, diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index 117fc0767c..b931112563 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -80,7 +80,7 @@ fn utxo_coin_fields_for_test(rpc_client: UtxoRpcClientEnum, force_seed: Option<& is_pos: false, requires_notarization: false.into(), overwintered: true, - segwit: false, + segwit: true, tx_version: 4, default_address_format: UtxoAddressFormat::Standard, asset_chain: true, @@ -2904,3 +2904,50 @@ fn test_parse_tx_with_huge_locktime() { let verbose_tx: RpcTransaction = json::from_str(verbose).expect("!json::from_str"); let _: UtxoTx = deserialize(verbose_tx.hex.as_slice()).unwrap(); } + +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_withdraw_to_p2sh() { + UtxoStandardCoin::ordered_mature_unspents.mock_safe(|coin, _| { + let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + }]; + MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + }); + + let client = NativeClient(Arc::new(NativeClientImpl::default())); + + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client.clone()), None); + + // Create a p2sh address for the test coin + let checksum_type = ChecksumType::DSHA256; + let seed = "spice describe gravity federal blast come thank unfair canal monkey style afraid"; + let key_pair = key_pair_from_seed(seed).unwrap(); + let p2sh_address = Address { + prefix: 85, + hash: key_pair.public().address_hash(), + t_addr_prefix: 0, + checksum_type, + hrp: None, + addr_format: UtxoAddressFormat::Standard, + }; + + let withdraw_req = WithdrawRequest { + amount: 1.into(), + to: p2sh_address.to_string(), + coin: TEST_COIN_NAME.into(), + max: false, + fee: None, + }; + let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let transaction: UtxoTx = deserialize(tx_details.tx_hex.as_slice()).unwrap(); + let output_script: Script = transaction.outputs[0].script_pubkey.clone().into(); + + assert!(output_script.is_pay_to_script_hash()); +} From 3b333323c0a19c39dd891b799ec6d13a55fbe849 Mon Sep 17 00:00:00 2001 From: shamardy Date: Mon, 2 Aug 2021 15:08:18 +0200 Subject: [PATCH 2/3] fix fmt warning --- mm2src/coins/utxo/utxo_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index b931112563..a1a9b1a3a9 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -2948,6 +2948,6 @@ fn test_withdraw_to_p2sh() { let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); let transaction: UtxoTx = deserialize(tx_details.tx_hex.as_slice()).unwrap(); let output_script: Script = transaction.outputs[0].script_pubkey.clone().into(); - + assert!(output_script.is_pay_to_script_hash()); } From 51498fb1131fcecc51d80f92a6a72df09d6f382f Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 3 Aug 2021 16:18:55 +0200 Subject: [PATCH 3/3] review fixes --- mm2src/coins/utxo.rs | 15 +- mm2src/coins/utxo/rpc_clients.rs | 6 +- mm2src/coins/utxo/utxo_common.rs | 27 ++-- mm2src/coins/utxo/utxo_tests.rs | 197 +++++++++++++++++++------ mm2src/mm2_bitcoin/keys/src/address.rs | 1 + 5 files changed, 180 insertions(+), 66 deletions(-) diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index 9073c3e72d..ed7d9858da 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -47,7 +47,7 @@ use futures::lock::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard}; use futures::stream::StreamExt; use futures01::Future; use keys::bytes::Bytes; -pub use keys::{Address, AddressFormat as UtxoAddressFormat, KeyPair, Private, Public, Secret, Type}; +pub use keys::{Address, AddressFormat as UtxoAddressFormat, KeyPair, Private, Public, Secret, Type as ScriptType}; #[cfg(test)] use mocktopus::macros::*; use num_traits::ToPrimitive; use primitives::hash::{H256, H264, H512}; @@ -1197,7 +1197,7 @@ pub trait UtxoCoinBuilder { hrp: conf.bech32_hrp.clone(), addr_format, }; - let my_script_pubkey = output_script(&my_address, true).to_bytes(); + let my_script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); let rpc_client = try_s!(self.rpc_client().await); let tx_fee = try_s!(self.tx_fee(&rpc_client).await); let decimals = try_s!(self.decimals(&rpc_client).await); @@ -1990,15 +1990,12 @@ fn script_sig(message: &H256, key_pair: &KeyPair, fork_id: u32) -> Result Script { +pub fn output_script(address: &Address, script_type: ScriptType) -> Script { match address.addr_format { UtxoAddressFormat::Segwit => Builder::build_p2wpkh(&address.hash), - _ => { - if is_p2pkh { - Builder::build_p2pkh(&address.hash) - } else { - Builder::build_p2sh(&address.hash) - } + _ => match script_type { + ScriptType::P2PKH => Builder::build_p2pkh(&address.hash), + ScriptType::P2SH => Builder::build_p2sh(&address.hash), }, } } diff --git a/mm2src/coins/utxo/rpc_clients.rs b/mm2src/coins/utxo/rpc_clients.rs index 52e3889eba..12dca7a5d5 100644 --- a/mm2src/coins/utxo/rpc_clients.rs +++ b/mm2src/coins/utxo/rpc_clients.rs @@ -26,7 +26,7 @@ use futures01::{Future, Sink, Stream}; use http::header::AUTHORIZATION; use http::Uri; use http::{Request, StatusCode}; -use keys::Address; +use keys::{Address, Type as ScriptType}; #[cfg(test)] use mocktopus::macros::*; use rpc::v1::types::{Bytes as BytesJson, Transaction as RpcTransaction, H256 as H256Json}; use serde_json::{self as json, Value as Json}; @@ -1478,7 +1478,7 @@ impl ElectrumClient { #[cfg_attr(test, mockable)] impl UtxoRpcClientOps for ElectrumClient { fn list_unspent(&self, address: &Address, _decimals: u8) -> UtxoRpcFut> { - let script = output_script(address, true); + let script = output_script(address, ScriptType::P2PKH); let script_hash = electrum_script_hash(&script); Box::new( self.scripthash_list_unspent(&hex::encode(script_hash)) @@ -1538,7 +1538,7 @@ impl UtxoRpcClientOps for ElectrumClient { } fn display_balance(&self, address: Address, decimals: u8) -> RpcRes { - let hash = electrum_script_hash(&output_script(&address, true)); + let hash = electrum_script_hash(&output_script(&address, ScriptType::P2PKH)); let hash_str = hex::encode(hash); Box::new(self.scripthash_get_balance(&hash_str).map(move |result| { BigDecimal::from(result.confirmed + result.unconfirmed) / BigDecimal::from(10u64.pow(decimals as u32)) diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 87f49a0794..d58e1941b1 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -15,7 +15,8 @@ use futures::compat::Future01CompatExt; use futures::future::{FutureExt, TryFutureExt}; use futures01::future::Either; use keys::bytes::Bytes; -use keys::{Address, AddressFormat as UtxoAddressFormat, AddressHash, KeyPair, Public, SegwitAddress, Type}; +use keys::{Address, AddressFormat as UtxoAddressFormat, AddressHash, KeyPair, Public, SegwitAddress, + Type as ScriptType}; use primitives::hash::H512; use rpc::v1::types::{Bytes as BytesJson, TransactionInputEnum, H256 as H256Json}; use script::{Builder, Opcode, Script, ScriptAddress, SignatureVersion, TransactionInputSigner, @@ -189,8 +190,8 @@ pub fn addresses_from_script(coin: &UtxoCoinFields, script: &Script) -> Result (conf.pub_addr_prefix, conf.pub_t_addr_prefix), - Type::P2SH => (conf.p2sh_addr_prefix, conf.p2sh_t_addr_prefix), + ScriptType::P2PKH => (conf.pub_addr_prefix, conf.pub_t_addr_prefix), + ScriptType::P2SH => (conf.p2sh_addr_prefix, conf.p2sh_t_addr_prefix), }; Address { @@ -290,7 +291,7 @@ where let dust: u64 = coin.as_ref().dust_amount; let lock_time = (now_ms() / 1000) as u32; - let change_script_pubkey = output_script(&coin.as_ref().my_address, true).to_bytes(); + let change_script_pubkey = output_script(&coin.as_ref().my_address, ScriptType::P2PKH).to_bytes(); let coin_tx_fee = match fee { Some(f) => f, None => coin.get_tx_fee().await?, @@ -738,7 +739,7 @@ where ); let fut = async move { let fee = try_s!(coin.get_htlc_spend_fee().await); - let script_pubkey = output_script(&coin.as_ref().my_address, true).to_bytes(); + let script_pubkey = output_script(&coin.as_ref().my_address, ScriptType::P2PKH).to_bytes(); let output = TransactionOutput { value: prev_tx.outputs[0].value - fee, script_pubkey, @@ -785,7 +786,7 @@ where ); let fut = async move { let fee = try_s!(coin.get_htlc_spend_fee().await); - let script_pubkey = output_script(&coin.as_ref().my_address, true).to_bytes(); + let script_pubkey = output_script(&coin.as_ref().my_address, ScriptType::P2PKH).to_bytes(); let output = TransactionOutput { value: prev_tx.outputs[0].value - fee, script_pubkey, @@ -829,7 +830,7 @@ where ); let fut = async move { let fee = try_s!(coin.get_htlc_spend_fee().await); - let script_pubkey = output_script(&coin.as_ref().my_address, true).to_bytes(); + let script_pubkey = output_script(&coin.as_ref().my_address, ScriptType::P2PKH).to_bytes(); let output = TransactionOutput { value: prev_tx.outputs[0].value - fee, script_pubkey, @@ -873,7 +874,7 @@ where ); let fut = async move { let fee = try_s!(coin.get_htlc_spend_fee().await); - let script_pubkey = output_script(&coin.as_ref().my_address, true).to_bytes(); + let script_pubkey = output_script(&coin.as_ref().my_address, ScriptType::P2PKH).to_bytes(); let output = TransactionOutput { value: prev_tx.outputs[0].value - fee, script_pubkey, @@ -1381,11 +1382,15 @@ where let is_p2pkh = to.prefix == conf.pub_addr_prefix && to.t_addr_prefix == conf.pub_t_addr_prefix; let is_p2sh = to.prefix == conf.p2sh_addr_prefix && to.t_addr_prefix == conf.p2sh_t_addr_prefix && conf.segwit; - if !is_p2pkh && !is_p2sh { + let script_type = if is_p2pkh { + ScriptType::P2PKH + } else if is_p2sh { + ScriptType::P2SH + } else { return MmError::err(WithdrawError::InvalidAddress("Expected either P2PKH or P2SH".into())); - } + }; - let script_pubkey = output_script(&to, is_p2pkh).to_bytes(); + let script_pubkey = output_script(&to, script_type).to_bytes(); let signature_version = match coin.as_ref().my_address.addr_format { UtxoAddressFormat::Segwit => SignatureVersion::WitnessV0, diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index a1a9b1a3a9..53a366b24d 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -19,6 +19,8 @@ use std::thread; use std::time::Duration; const TEST_COIN_NAME: &'static str = "RICK"; +// Made-up hrp for rick to test p2wpkh script +const TEST_COIN_HRP: &'static str = "rck"; pub fn electrum_client_for_test(servers: &[&str]) -> ElectrumClient { let client = ElectrumClientImpl::new(TEST_COIN_NAME.into(), Default::default()); @@ -48,7 +50,11 @@ pub fn electrum_client_for_test(servers: &[&str]) -> ElectrumClient { #[cfg(not(target_arch = "wasm32"))] fn native_client_for_test() -> NativeClient { NativeClient(Arc::new(NativeClientImpl::default())) } -fn utxo_coin_fields_for_test(rpc_client: UtxoRpcClientEnum, force_seed: Option<&str>) -> UtxoCoinFields { +fn utxo_coin_fields_for_test( + rpc_client: UtxoRpcClientEnum, + force_seed: Option<&str>, + is_segwit_coin: bool, +) -> UtxoCoinFields { let checksum_type = ChecksumType::DSHA256; let default_seed = "spice describe gravity federal blast come thank unfair canal monkey style afraid"; let seed = match force_seed { @@ -74,6 +80,11 @@ fn utxo_coin_fields_for_test(rpc_client: UtxoRpcClientEnum, force_seed: Option<& addr_format: UtxoAddressFormat::Standard, }; let my_script_pubkey = Builder::build_p2pkh(&my_address.hash).to_bytes(); + let bech32_hrp = if is_segwit_coin { + Some(TEST_COIN_HRP.to_string()) + } else { + None + }; UtxoCoinFields { conf: UtxoCoinConf { @@ -88,7 +99,7 @@ fn utxo_coin_fields_for_test(rpc_client: UtxoRpcClientEnum, force_seed: Option<& p2sh_t_addr_prefix: 0, pub_addr_prefix: 60, pub_t_addr_prefix: 0, - bech32_hrp: None, + bech32_hrp, ticker: TEST_COIN_NAME.into(), wif_prefix: 0, tx_fee_volatility_percent: DEFAULT_DYNAMIC_FEE_VOLATILITY_PERCENT, @@ -123,14 +134,18 @@ fn utxo_coin_from_fields(coin: UtxoCoinFields) -> UtxoStandardCoin { arc.into() } -fn utxo_coin_for_test(rpc_client: UtxoRpcClientEnum, force_seed: Option<&str>) -> UtxoStandardCoin { - utxo_coin_from_fields(utxo_coin_fields_for_test(rpc_client, force_seed)) +fn utxo_coin_for_test( + rpc_client: UtxoRpcClientEnum, + force_seed: Option<&str>, + is_segwit_coin: bool, +) -> UtxoStandardCoin { + utxo_coin_from_fields(utxo_coin_fields_for_test(rpc_client, force_seed, is_segwit_coin)) } #[test] fn test_extract_secret() { let client = electrum_client_for_test(&["electrum1.cipig.net:10017"]); - let coin = utxo_coin_for_test(client.into(), None); + let coin = utxo_coin_for_test(client.into(), None, false); let tx_hex = hex::decode("0100000001de7aa8d29524906b2b54ee2e0281f3607f75662cbc9080df81d1047b78e21dbc00000000d7473044022079b6c50820040b1fbbe9251ced32ab334d33830f6f8d0bf0a40c7f1336b67d5b0220142ccf723ddabb34e542ed65c395abc1fbf5b6c3e730396f15d25c49b668a1a401209da937e5609680cb30bff4a7661364ca1d1851c2506fa80c443f00a3d3bf7365004c6b6304f62b0e5cb175210270e75970bb20029b3879ec76c4acd320a8d0589e003636264d01a7d566504bfbac6782012088a9142fb610d856c19fd57f2d0cffe8dff689074b3d8a882103f368228456c940ac113e53dad5c104cf209f2f102a409207269383b6ab9b03deac68ffffffff01d0dc9800000000001976a9146d9d2b554d768232320587df75c4338ecc8bf37d88ac40280e5c").unwrap(); let expected_secret = hex::decode("9da937e5609680cb30bff4a7661364ca1d1851c2506fa80c443f00a3d3bf7365").unwrap(); @@ -142,7 +157,7 @@ fn test_extract_secret() { #[test] fn test_generate_transaction() { let client = electrum_client_for_test(&["electrum1.cipig.net:10017"]); - let coin = utxo_coin_for_test(client.into(), None); + let coin = utxo_coin_for_test(client.into(), None, false); let unspents = vec![UnspentInfo { value: 10000000000, outpoint: OutPoint::default(), @@ -221,7 +236,7 @@ fn test_generate_transaction() { #[test] fn test_addresses_from_script() { let client = electrum_client_for_test(&["electrum1.cipig.net:10017", "electrum2.cipig.net:10017"]); - let coin = utxo_coin_for_test(client.into(), None); + let coin = utxo_coin_for_test(client.into(), None, false); // P2PKH let script: Script = "76a91405aab5342166f8594baf17a7d9bef5d56744332788ac".into(); let expected_addr: Vec
= vec!["R9o9xTocqr6CeEDGDH6mEYpwLoMz6jNjMW".into()]; @@ -350,7 +365,7 @@ fn test_wait_for_payment_spend_timeout_native() { MockResult::Return(Box::new(futures01::future::ok(None))) }); let client = UtxoRpcClientEnum::Native(NativeClient(Arc::new(client))); - let coin = utxo_coin_for_test(client, None); + let coin = utxo_coin_for_test(client, None, false); let transaction = hex::decode("01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000494830450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac000247304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee0121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635711000000") .unwrap(); let wait_until = now_ms() / 1000 - 1; @@ -373,7 +388,7 @@ fn test_wait_for_payment_spend_timeout_electrum() { let client = ElectrumClientImpl::new(TEST_COIN_NAME.into(), Default::default()); let client = UtxoRpcClientEnum::Electrum(ElectrumClient(Arc::new(client))); - let coin = utxo_coin_for_test(client, None); + let coin = utxo_coin_for_test(client, None, false); let transaction = hex::decode("01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000494830450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac000247304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee0121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635711000000") .unwrap(); let wait_until = now_ms() / 1000 - 1; @@ -393,6 +408,7 @@ fn test_search_for_swap_tx_spend_electrum_was_spent() { let coin = utxo_coin_for_test( client.into(), Some("spice describe gravity federal blast come thank unfair canal monkey style afraid"), + false, ); // raw tx bytes of https://rick.kmd.dev/tx/ba881ecca15b5d4593f14f25debbcdfe25f101fd2e9cf8d0b5d92d19813d4424 @@ -425,6 +441,7 @@ fn test_search_for_swap_tx_spend_electrum_was_refunded() { let coin = utxo_coin_for_test( client.into(), Some("spice describe gravity federal blast come thank unfair canal monkey style afraid"), + false, ); // raw tx bytes of https://rick.kmd.dev/tx/78ea7839f6d1b0dafda2ba7e34c1d8218676a58bd1b33f03a5f76391f61b72b0 @@ -468,7 +485,7 @@ fn test_withdraw_impl_set_fixed_fee() { let client = NativeClient(Arc::new(NativeClientImpl::default())); - let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None); + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, false); let withdraw_req = WithdrawRequest { amount: 1.into(), @@ -507,7 +524,7 @@ fn test_withdraw_impl_sat_per_kb_fee() { let client = NativeClient(Arc::new(NativeClientImpl::default())); - let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None); + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, false); let withdraw_req = WithdrawRequest { amount: 1.into(), @@ -549,7 +566,7 @@ fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max() { let client = NativeClient(Arc::new(NativeClientImpl::default())); - let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None); + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, false); let withdraw_req = WithdrawRequest { amount: "9.9789".parse().unwrap(), @@ -593,7 +610,7 @@ fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max_dust_included_to_fee() let client = NativeClient(Arc::new(NativeClientImpl::default())); - let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None); + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, false); let withdraw_req = WithdrawRequest { amount: "9.9789".parse().unwrap(), @@ -637,7 +654,7 @@ fn test_withdraw_impl_sat_per_kb_fee_amount_over_max() { let client = NativeClient(Arc::new(NativeClientImpl::default())); - let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None); + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, false); let withdraw_req = WithdrawRequest { amount: "9.97939455".parse().unwrap(), @@ -669,7 +686,7 @@ fn test_withdraw_impl_sat_per_kb_fee_max() { let client = NativeClient(Arc::new(NativeClientImpl::default())); - let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None); + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, false); let withdraw_req = WithdrawRequest { amount: 0.into(), @@ -726,7 +743,7 @@ fn test_withdraw_kmd_rewards_impl( let client = NativeClient(Arc::new(NativeClientImpl::default())); - let mut fields = utxo_coin_fields_for_test(UtxoRpcClientEnum::Native(client), None); + let mut fields = utxo_coin_fields_for_test(UtxoRpcClientEnum::Native(client), None, false); fields.conf.ticker = "KMD".to_owned(); let coin = utxo_coin_from_fields(fields); @@ -800,7 +817,7 @@ fn test_withdraw_rick_rewards_none() { let client = NativeClient(Arc::new(NativeClientImpl::default())); - let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None); + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, false); let withdraw_req = WithdrawRequest { amount: BigDecimal::from_str("0.00001").unwrap(), @@ -823,6 +840,7 @@ fn test_ordered_mature_unspents_without_tx_cache() { let coin = utxo_coin_for_test( client.into(), Some("spice describe gravity federal blast come thank unfair canal monkey style afraid"), + false, ); assert!(coin.as_ref().tx_cache_directory.is_none()); assert_ne!( @@ -839,7 +857,7 @@ fn test_ordered_mature_unspents_without_tx_cache() { fn test_utxo_lock() { // send several transactions concurrently to check that they are not using same inputs let client = electrum_client_for_test(&["electrum1.cipig.net:10017", "electrum2.cipig.net:10017"]); - let coin = utxo_coin_for_test(client.into(), None); + let coin = utxo_coin_for_test(client.into(), None, false); let output = TransactionOutput { value: 1000000, script_pubkey: Builder::build_p2pkh(&coin.as_ref().my_address.hash).to_bytes(), @@ -929,6 +947,7 @@ fn get_tx_details_coinbase_transaction() { let coin = utxo_coin_for_test( client.into(), Some("spice describe gravity federal blast come thank unfair canal monkey style afraid"), + false, ); let fut = async move { @@ -1054,7 +1073,7 @@ fn test_generate_transaction_relay_fee_is_used_when_dynamic_fee_is_lower() { MockResult::Return(Box::new(futures01::future::ok("1.0".parse().unwrap()))) }); let client = UtxoRpcClientEnum::Native(NativeClient(Arc::new(client))); - let mut coin = utxo_coin_fields_for_test(client, None); + let mut coin = utxo_coin_fields_for_test(client, None, false); coin.conf.force_min_relay_fee = true; let coin = utxo_coin_from_fields(coin); let unspents = vec![UnspentInfo { @@ -1098,7 +1117,7 @@ fn test_generate_tx_fee_is_correct_when_dynamic_fee_is_larger_than_relay() { MockResult::Return(Box::new(futures01::future::ok("0.00001".parse().unwrap()))) }); let client = UtxoRpcClientEnum::Native(NativeClient(Arc::new(client))); - let mut coin = utxo_coin_fields_for_test(client, None); + let mut coin = utxo_coin_fields_for_test(client, None, false); coin.conf.force_min_relay_fee = true; let coin = utxo_coin_from_fields(coin); let unspents = vec![ @@ -1681,7 +1700,7 @@ fn test_ordered_mature_unspents_from_cache_impl( }); // run test - let coin = utxo_coin_for_test(UtxoRpcClientEnum::Electrum(client), None); + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Electrum(client), None, false); let (unspents, _) = block_on(coin.ordered_mature_unspents(&Address::from("R9o9xTocqr6CeEDGDH6mEYpwLoMz6jNjMW"))) .expect("Expected an empty unspent list"); // unspents should be empty because `is_unspent_mature()` always returns false @@ -1819,7 +1838,7 @@ fn test_ordered_mature_unspents_from_cache() { #[cfg(not(target_arch = "wasm32"))] fn test_native_client_unspents_filtered_using_tx_cache_single_tx_in_cache() { let client = native_client_for_test(); - let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None); + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, false); let address: Address = "RGfFZaaNV68uVe1uMf6Y37Y8E1i2SyYZBN".into(); block_on(coin.as_ref().recently_spent_outpoints.lock()).for_script_pubkey = @@ -1866,7 +1885,7 @@ fn test_native_client_unspents_filtered_using_tx_cache_single_tx_in_cache() { #[cfg(not(target_arch = "wasm32"))] fn test_native_client_unspents_filtered_using_tx_cache_single_several_chained_txs_in_cache() { let client = native_client_for_test(); - let coin = utxo_coin_fields_for_test(UtxoRpcClientEnum::Native(client), None); + let coin = utxo_coin_fields_for_test(UtxoRpcClientEnum::Native(client), None, false); let address: Address = "RGfFZaaNV68uVe1uMf6Y37Y8E1i2SyYZBN".into(); block_on(coin.recently_spent_outpoints.lock()).for_script_pubkey = Builder::build_p2pkh(&address.hash).to_bytes(); @@ -2219,7 +2238,7 @@ fn test_qtum_is_unspent_mature() { use crate::utxo::qtum::QtumBasedCoin; use rpc::v1::types::{ScriptType, SignedTransactionOutput, TransactionOutputScript}; - let mut coin_fields = utxo_coin_fields_for_test(UtxoRpcClientEnum::Native(native_client_for_test()), None); + let mut coin_fields = utxo_coin_fields_for_test(UtxoRpcClientEnum::Native(native_client_for_test()), None, false); // Qtum's mature confirmations is 500 blocks coin_fields.conf.mature_confirmations = 500; let arc: UtxoArc = coin_fields.into(); @@ -2288,6 +2307,7 @@ fn test_get_sender_trade_fee_dynamic_tx_fee() { let mut coin_fields = utxo_coin_fields_for_test( UtxoRpcClientEnum::Electrum(rpc_client), Some("bob passphrase max taker vol with dynamic trade fee"), + false, ); coin_fields.tx_fee = TxFee::Dynamic(EstimateFeeMethod::Standard); let coin = utxo_coin_from_fields(coin_fields); @@ -2330,7 +2350,7 @@ fn test_validate_fee_wrong_sender() { "electrum2.cipig.net:10018", "electrum3.cipig.net:10018", ]); - let coin = utxo_coin_for_test(UtxoRpcClientEnum::Electrum(rpc_client), None); + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Electrum(rpc_client), None, false); // https://morty.explorer.dexstats.info/tx/fe4b0e1c4537e22f2956b5b74513fc936ebd87ada21513e850899cb07a45d475 let tx_bytes = hex::decode("0400008085202f890199cc492c24cc617731d13cff0ef22e7b0c277a64e7368a615b46214424a1c894020000006a473044022071edae37cf518e98db3f7637b9073a7a980b957b0c7b871415dbb4898ec3ebdc022031b402a6b98e64ffdf752266449ca979a9f70144dba77ed7a6a25bfab11648f6012103ad6f89abc2e5beaa8a3ac28e22170659b3209fe2ddf439681b4b8f31508c36faffffffff0202290200000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac8a96e70b000000001976a914d55f0df6cb82630ad21a4e6049522a6f2b6c9d4588ac8afb2c60000000000000000000000000000000").unwrap(); let taker_fee_tx = coin.tx_enum_from_bytes(&tx_bytes).unwrap(); @@ -2355,7 +2375,7 @@ fn test_validate_fee_min_block() { "electrum2.cipig.net:10018", "electrum3.cipig.net:10018", ]); - let coin = utxo_coin_for_test(UtxoRpcClientEnum::Electrum(rpc_client), None); + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Electrum(rpc_client), None, false); // https://morty.explorer.dexstats.info/tx/fe4b0e1c4537e22f2956b5b74513fc936ebd87ada21513e850899cb07a45d475 let tx_bytes = hex::decode("0400008085202f890199cc492c24cc617731d13cff0ef22e7b0c277a64e7368a615b46214424a1c894020000006a473044022071edae37cf518e98db3f7637b9073a7a980b957b0c7b871415dbb4898ec3ebdc022031b402a6b98e64ffdf752266449ca979a9f70144dba77ed7a6a25bfab11648f6012103ad6f89abc2e5beaa8a3ac28e22170659b3209fe2ddf439681b4b8f31508c36faffffffff0202290200000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac8a96e70b000000001976a914d55f0df6cb82630ad21a4e6049522a6f2b6c9d4588ac8afb2c60000000000000000000000000000000").unwrap(); let taker_fee_tx = coin.tx_enum_from_bytes(&tx_bytes).unwrap(); @@ -2376,7 +2396,7 @@ fn test_validate_fee_bch_70_bytes_signature() { "electrum2.cipig.net:10055", "electrum3.cipig.net:10055", ]); - let coin = utxo_coin_for_test(UtxoRpcClientEnum::Electrum(rpc_client), None); + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Electrum(rpc_client), None, false); // https://blockchair.com/bitcoin-cash/transaction/ccee05a6b5bbc6f50d2a65a5a3a04690d3e2d81082ad57d3ab471189f53dd70d let tx_bytes = hex::decode("0100000002cae89775f264e50f14238be86a7184b7f77bfe26f54067b794c546ec5eb9c91a020000006b483045022100d6ed080f722a0637a37552382f462230cc438984bc564bdb4b7094f06cfa38fa022062304a52602df1fbb3bebac4f56e1632ad456f62d9031f4983f07e546c8ec4d8412102ae7dc4ef1b49aadeff79cfad56664105f4d114e1716bc4f930cb27dbd309e521ffffffff11f386a6fe8f0431cb84f549b59be00f05e78f4a8a926c5e023a0d5f9112e8200000000069463043021f17eb93ed20a6f2cd357eabb41a4ec6329000ddc6d5b42ecbe642c5d41b206a022026bc4920c4ce3af751283574baa8e4a3efd4dad0d8fe6ba3ddf5d75628d36fda412102ae7dc4ef1b49aadeff79cfad56664105f4d114e1716bc4f930cb27dbd309e521ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac57481c00000000001976a914bac11ce4cd2b1df2769c470d09b54f86df737e3c88ac035b4a60").unwrap(); let taker_fee_tx = coin.tx_enum_from_bytes(&tx_bytes).unwrap(); @@ -2450,7 +2470,7 @@ fn firo_lelantus_tx_details() { "electrumx02.firo.org:50001", "electrumx03.firo.org:50001", ]); - let coin = utxo_coin_for_test(electrum.into(), None); + let coin = utxo_coin_for_test(electrum.into(), None, false); let mut map = HashMap::new(); let tx_hash = hex::decode("ad812911f5cba3eab7c193b6cd7020ea02fb5c25634ae64959c3171a6bd5a74d").unwrap(); @@ -2760,7 +2780,7 @@ fn test_tx_details_kmd_rewards() { "electrum2.cipig.net:10001", "electrum3.cipig.net:10001", ]); - let mut fields = utxo_coin_fields_for_test(electrum.into(), None); + let mut fields = utxo_coin_fields_for_test(electrum.into(), None, false); fields.conf.ticker = "KMD".to_owned(); fields.my_address = Address::from("RMGJ9tRST45RnwEKHPGgBLuY3moSYP7Mhk"); let coin = utxo_coin_from_fields(fields); @@ -2792,7 +2812,7 @@ fn test_tx_details_kmd_rewards_claimed_by_other() { "electrum2.cipig.net:10001", "electrum3.cipig.net:10001", ]); - let mut fields = utxo_coin_fields_for_test(electrum.into(), None); + let mut fields = utxo_coin_fields_for_test(electrum.into(), None, false); fields.conf.ticker = "KMD".to_owned(); fields.my_address = Address::from("RMGJ9tRST45RnwEKHPGgBLuY3moSYP7Mhk"); let coin = utxo_coin_from_fields(fields); @@ -2822,7 +2842,7 @@ fn test_tx_details_bch_no_rewards() { "bch0.kister.net:51001", "testnet.imaginary.cash:50001", ]); - let coin = utxo_coin_for_test(electrum.into(), None); + let coin = utxo_coin_for_test(electrum.into(), None, false); let mut input_transactions = HistoryUtxoTxMap::new(); let hash = hex::decode("eb13d926f15cbb896e0bcc7a1a77a4ec63504e57a1524c13a7a9b80f43ecb05c").unwrap(); @@ -2845,7 +2865,7 @@ fn test_update_kmd_rewards() { "electrum2.cipig.net:10001", "electrum3.cipig.net:10001", ]); - let mut fields = utxo_coin_fields_for_test(electrum.into(), None); + let mut fields = utxo_coin_fields_for_test(electrum.into(), None, false); fields.conf.ticker = "KMD".to_owned(); fields.my_address = Address::from("RMGJ9tRST45RnwEKHPGgBLuY3moSYP7Mhk"); let coin = utxo_coin_from_fields(fields); @@ -2876,7 +2896,7 @@ fn test_update_kmd_rewards_claimed_not_by_me() { "electrum2.cipig.net:10001", "electrum3.cipig.net:10001", ]); - let mut fields = utxo_coin_fields_for_test(electrum.into(), None); + let mut fields = utxo_coin_fields_for_test(electrum.into(), None, false); fields.conf.ticker = "KMD".to_owned(); fields.my_address = Address::from("RMGJ9tRST45RnwEKHPGgBLuY3moSYP7Mhk"); let coin = utxo_coin_from_fields(fields); @@ -2905,6 +2925,52 @@ fn test_parse_tx_with_huge_locktime() { let _: UtxoTx = deserialize(verbose_tx.hex.as_slice()).unwrap(); } +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_withdraw_to_p2pkh() { + UtxoStandardCoin::ordered_mature_unspents.mock_safe(|coin, _| { + let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + }]; + MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + }); + + let client = NativeClient(Arc::new(NativeClientImpl::default())); + + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client.clone()), None, false); + + // Create a p2pkh address for the test coin + let p2pkh_address = Address { + prefix: coin.as_ref().conf.pub_addr_prefix, + hash: coin.as_ref().my_address.hash.clone(), + t_addr_prefix: coin.as_ref().conf.pub_t_addr_prefix, + checksum_type: coin.as_ref().my_address.checksum_type, + hrp: coin.as_ref().conf.bech32_hrp.clone(), + addr_format: UtxoAddressFormat::Standard, + }; + + let withdraw_req = WithdrawRequest { + amount: 1.into(), + to: p2pkh_address.to_string(), + coin: TEST_COIN_NAME.into(), + max: false, + fee: None, + }; + let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let transaction: UtxoTx = deserialize(tx_details.tx_hex.as_slice()).unwrap(); + let output_script: Script = transaction.outputs[0].script_pubkey.clone().into(); + + let expected_script = Builder::build_p2pkh(&p2pkh_address.hash); + + assert_eq!(output_script, expected_script); +} + #[test] #[cfg(not(target_arch = "wasm32"))] fn test_withdraw_to_p2sh() { @@ -2923,18 +2989,15 @@ fn test_withdraw_to_p2sh() { let client = NativeClient(Arc::new(NativeClientImpl::default())); - let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client.clone()), None); + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client.clone()), None, false); // Create a p2sh address for the test coin - let checksum_type = ChecksumType::DSHA256; - let seed = "spice describe gravity federal blast come thank unfair canal monkey style afraid"; - let key_pair = key_pair_from_seed(seed).unwrap(); let p2sh_address = Address { - prefix: 85, - hash: key_pair.public().address_hash(), - t_addr_prefix: 0, - checksum_type, - hrp: None, + prefix: coin.as_ref().conf.p2sh_addr_prefix, + hash: coin.as_ref().my_address.hash.clone(), + t_addr_prefix: coin.as_ref().conf.p2sh_t_addr_prefix, + checksum_type: coin.as_ref().my_address.checksum_type, + hrp: coin.as_ref().conf.bech32_hrp.clone(), addr_format: UtxoAddressFormat::Standard, }; @@ -2949,5 +3012,53 @@ fn test_withdraw_to_p2sh() { let transaction: UtxoTx = deserialize(tx_details.tx_hex.as_slice()).unwrap(); let output_script: Script = transaction.outputs[0].script_pubkey.clone().into(); - assert!(output_script.is_pay_to_script_hash()); + let expected_script = Builder::build_p2sh(&p2sh_address.hash); + + assert_eq!(output_script, expected_script); +} + +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_withdraw_to_p2wpkh() { + UtxoStandardCoin::ordered_mature_unspents.mock_safe(|coin, _| { + let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + }]; + MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + }); + + let client = NativeClient(Arc::new(NativeClientImpl::default())); + + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client.clone()), None, true); + + // Create a p2wpkh address for the test coin + let p2wpkh_address = Address { + prefix: coin.as_ref().conf.pub_addr_prefix, + hash: coin.as_ref().my_address.hash.clone(), + t_addr_prefix: coin.as_ref().conf.pub_t_addr_prefix, + checksum_type: coin.as_ref().my_address.checksum_type, + hrp: coin.as_ref().conf.bech32_hrp.clone(), + addr_format: UtxoAddressFormat::Segwit, + }; + + let withdraw_req = WithdrawRequest { + amount: 1.into(), + to: p2wpkh_address.to_string(), + coin: TEST_COIN_NAME.into(), + max: false, + fee: None, + }; + let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let transaction: UtxoTx = deserialize(tx_details.tx_hex.as_slice()).unwrap(); + let output_script: Script = transaction.outputs[0].script_pubkey.clone().into(); + + let expected_script = Builder::build_p2wpkh(&p2wpkh_address.hash); + + assert_eq!(output_script, expected_script); } diff --git a/mm2src/mm2_bitcoin/keys/src/address.rs b/mm2src/mm2_bitcoin/keys/src/address.rs index 5c0c5ced00..e52c9678b8 100644 --- a/mm2src/mm2_bitcoin/keys/src/address.rs +++ b/mm2src/mm2_bitcoin/keys/src/address.rs @@ -66,6 +66,7 @@ impl AddressFormat { pub fn is_legacy(&self) -> bool { matches!(*self, AddressFormat::Standard) } } +// TODO add ScriptType field to this struct for easier use of output_script function /// `AddressHash` with prefix and t addr zcash prefix #[derive(Debug, PartialEq, Clone)] pub struct Address {