From 738258811bd4bcd829e9b307767322451d456fc5 Mon Sep 17 00:00:00 2001 From: dimxy Date: Wed, 20 Dec 2023 01:02:03 +0500 Subject: [PATCH 01/82] feat(tx): add new sign_raw_transaction rpc for UTXO and EVM coins (#1930) --- mm2src/coins/eth.rs | 104 +++++-- mm2src/coins/eth/eth_wasm_tests.rs | 27 +- mm2src/coins/lightning.rs | 28 +- mm2src/coins/lightning/ln_events.rs | 7 +- mm2src/coins/lp_coins.rs | 100 ++++++- mm2src/coins/qrc20.rs | 41 ++- .../rpc_command/lightning/open_channel.rs | 3 +- mm2src/coins/solana.rs | 27 +- mm2src/coins/solana/spl.rs | 19 +- mm2src/coins/tendermint/tendermint_coin.rs | 28 +- mm2src/coins/tendermint/tendermint_token.rs | 29 +- mm2src/coins/test_coin.rs | 10 +- mm2src/coins/utxo.rs | 20 +- mm2src/coins/utxo/bch.rs | 27 +- mm2src/coins/utxo/qtum.rs | 25 +- mm2src/coins/utxo/qtum_delegation.rs | 5 +- mm2src/coins/utxo/slp.rs | 32 ++- mm2src/coins/utxo/utxo_common.rs | 272 +++++++++++++++++- mm2src/coins/utxo/utxo_standard.rs | 25 +- mm2src/coins/utxo/utxo_tests.rs | 2 +- mm2src/coins/utxo/utxo_withdraw.rs | 9 +- mm2src/coins/utxo_signer/src/with_key_pair.rs | 11 +- mm2src/coins/z_coin.rs | 24 +- mm2src/mm2_bitcoin/keys/src/address.rs | 2 +- mm2src/mm2_bitcoin/keys/src/private.rs | 3 +- mm2src/mm2_bitcoin/script/src/builder.rs | 2 +- mm2src/mm2_bitcoin/script/src/script.rs | 6 +- .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 6 +- mm2src/mm2_main/tests/mm2_tests/eth_tests.rs | 79 +++++ .../tests/mm2_tests/mm2_tests_inner.rs | 113 ++++++++ mm2src/mm2_test_helpers/src/for_tests.rs | 24 ++ 31 files changed, 945 insertions(+), 165 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 90e2f90ffb..1c068a26f8 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -1,5 +1,5 @@ /****************************************************************************** - * Copyright © 2023 Pampex LTD and TillyHK LTD * + * Copyright © 2023 Pampex LTD and TillyHK LTD * * * * See the CONTRIBUTOR-LICENSE-AGREEMENT, COPYING, LICENSE-COPYRIGHT-NOTICE * * and DEVELOPER-CERTIFICATE-OF-ORIGIN files in the LEGAL directory in * @@ -7,7 +7,7 @@ * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * - * Komodo DeFi Framework software, including this file may be copied, modified, propagated* + * Komodo DeFi Framework software, including this file may be copied, modified, propagated * * or distributed except according to the terms contained in the * * LICENSE-COPYRIGHT-NOTICE file. * * * @@ -89,17 +89,17 @@ use super::{coin_conf, lp_coinfind_or_err, AsyncMutex, BalanceError, BalanceFut, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionRes, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, RewardTarget, RpcClientType, RpcTransportEventHandler, RpcTransportEventHandlerShared, - SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, - SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, - TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionDetails, - TransactionEnum, TransactionErr, TransactionFut, TransactionType, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, - VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult, EARLY_CONFIRMATION_ERR_LOG, - INVALID_CONTRACT_ADDRESS_ERR_LOG, INVALID_PAYMENT_STATE_ERR_LOG, INVALID_RECEIVER_ERR_LOG, - INVALID_SENDER_ERR_LOG, INVALID_SWAP_ID_ERR_LOG}; + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignEthTransactionParams, + SignRawTransactionEnum, SignRawTransactionRequest, SignatureError, SignatureResult, SpendPaymentArgs, + SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, + TradePreimageValue, Transaction, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, + TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult, + EARLY_CONFIRMATION_ERR_LOG, INVALID_CONTRACT_ADDRESS_ERR_LOG, INVALID_PAYMENT_STATE_ERR_LOG, + INVALID_RECEIVER_ERR_LOG, INVALID_SENDER_ERR_LOG, INVALID_SWAP_ID_ERR_LOG}; pub use rlp; #[cfg(test)] mod eth_tests; @@ -2058,6 +2058,7 @@ impl WatcherOps for EthCoin { } } +#[async_trait] #[cfg_attr(test, mockable)] impl MarketCoinOps for EthCoin { fn ticker(&self) -> &str { &self.ticker[..] } @@ -2169,6 +2170,14 @@ impl MarketCoinOps for EthCoin { ) } + async fn sign_raw_tx(&self, args: &SignRawTransactionRequest) -> RawTransactionResult { + if let SignRawTransactionEnum::ETH(eth_args) = &args.tx { + sign_raw_eth_tx(self, eth_args).await + } else { + MmError::err(RawTransactionError::InvalidParam("eth type expected".to_string())) + } + } + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send> { macro_rules! update_status_with_error { ($status: ident, $error: ident) => { @@ -2406,7 +2415,7 @@ lazy_static! { type EthTxFut = Box + Send + 'static>; -async fn sign_and_send_transaction_with_keypair( +async fn sign_transaction_with_keypair( ctx: MmArc, coin: &EthCoin, key_pair: &KeyPair, @@ -2414,11 +2423,11 @@ async fn sign_and_send_transaction_with_keypair( action: Action, data: Vec, gas: U256, -) -> Result { +) -> Result<(SignedEthTx, Vec), TransactionErr> { let mut status = ctx.log.status_handle(); macro_rules! tags { () => { - &[&"sign-and-send"] + &[&"sign"] }; } let _nonce_lock = coin.nonce_lock.lock().await; @@ -2440,7 +2449,29 @@ async fn sign_and_send_transaction_with_keypair( data, }; - let signed = tx.sign(key_pair.secret(), coin.chain_id); + Ok(( + tx.sign(key_pair.secret(), coin.chain_id), + web3_instances_with_latest_nonce, + )) +} + +async fn sign_and_send_transaction_with_keypair( + ctx: MmArc, + coin: &EthCoin, + key_pair: &KeyPair, + value: U256, + action: Action, + data: Vec, + gas: U256, +) -> Result { + let mut status = ctx.log.status_handle(); + macro_rules! tags { + () => { + &[&"sign-and-send"] + }; + } + let (signed, web3_instances_with_latest_nonce) = + sign_transaction_with_keypair(ctx, coin, key_pair, value, action, data, gas).await?; let bytes = Bytes(rlp::encode(&signed).to_vec()); status.status(tags!(), "send_raw_transaction…"); @@ -2450,7 +2481,8 @@ async fn sign_and_send_transaction_with_keypair( try_tx_s!(select_ok(futures).await.map_err(|e| ERRL!("{}", e)), signed); status.status(tags!(), "get_addr_nonce…"); - coin.wait_for_addr_nonce_increase(coin.my_address, nonce).await; + coin.wait_for_addr_nonce_increase(coin.my_address, signed.transaction.unsigned.nonce) + .await; Ok(signed) } @@ -2502,6 +2534,42 @@ async fn sign_and_send_transaction_with_metamask( } } +/// Sign eth transaction +async fn sign_raw_eth_tx(coin: &EthCoin, args: &SignEthTransactionParams) -> RawTransactionResult { + let ctx = MmArc::from_weak(&coin.ctx) + .ok_or("!ctx") + .map_to_mm(|err| RawTransactionError::TransactionError(err.to_string()))?; + let value = wei_from_big_decimal(args.value.as_ref().unwrap_or(&BigDecimal::from(0)), coin.decimals)?; + let action = if let Some(to) = &args.to { + Call(Address::from_str(to).map_to_mm(|err| RawTransactionError::InvalidParam(err.to_string()))?) + } else { + Create + }; + let data = hex::decode(args.data.as_ref().unwrap_or(&String::from("")))?; + match coin.priv_key_policy { + // TODO: use zeroise for privkey + EthPrivKeyPolicy::Iguana(ref key_pair) + | EthPrivKeyPolicy::HDWallet { + activated_key: ref key_pair, + .. + } => { + return sign_transaction_with_keypair(ctx, coin, key_pair, value, action, data, args.gas_limit) + .await + .map(|(signed_tx, _)| RawTransactionRes { + tx_hex: signed_tx.tx_hex().into(), + }) + .map_to_mm(|err| RawTransactionError::TransactionError(err.get_plain_text_format())); + }, + #[cfg(target_arch = "wasm32")] + EthPrivKeyPolicy::Metamask(_) => MmError::err(RawTransactionError::InvalidParam( + "sign raw eth tx not implemented for Metamask".into(), + )), + EthPrivKeyPolicy::Trezor => MmError::err(RawTransactionError::InvalidParam( + "sign raw eth tx not implemented for Trezor".into(), + )), + } +} + impl EthCoin { /// Downloads and saves ETH transaction history of my_address, relies on Parity trace_filter API /// https://wiki.parity.io/JSONRPC-trace-module#trace_filter, this requires tracing to be enabled diff --git a/mm2src/coins/eth/eth_wasm_tests.rs b/mm2src/coins/eth/eth_wasm_tests.rs index cc0e7de4ec..1b4e7de7ed 100644 --- a/mm2src/coins/eth/eth_wasm_tests.rs +++ b/mm2src/coins/eth/eth_wasm_tests.rs @@ -70,8 +70,7 @@ async fn test_send() { console::log_1(&format!("{:?}", block).into()); } -#[wasm_bindgen_test] -async fn test_init_eth_coin() { +async fn init_eth_coin_helper() -> Result<(MmArc, MmCoinEnum), String> { let conf = json!({ "coins": [{ "coin": "ETH", @@ -96,5 +95,27 @@ async fn test_init_eth_coin() { "urls":[ETH_DEV_NODE], "swap_contract_address":ETH_DEV_SWAP_CONTRACT }); - let _coin = lp_coininit(&ctx, "ETH", &req).await.unwrap(); + Ok((ctx.clone(), lp_coininit(&ctx, "ETH", &req).await?)) +} + +#[wasm_bindgen_test] +async fn test_init_eth_coin() { let (_ctx, _coin) = init_eth_coin_helper().await.unwrap(); } + +#[wasm_bindgen_test] +async fn wasm_test_sign_eth_tx() { + // we need to hold ref to _ctx until the end of the test (because of the weak ref to MmCtx in EthCoinImpl) + let (_ctx, coin) = init_eth_coin_helper().await.unwrap(); + let sign_req = json::from_value(json!({ + "coin": "ETH", + "type": "ETH", + "tx": { + "to": "0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94".to_string(), + "value": "1.234", + "gas_limit": "21000" + } + })) + .unwrap(); + let res = coin.sign_raw_tx(&sign_req).await; + console::log_1(&format!("res={:?}", res).into()); + assert!(res.is_ok()); } diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index 447990e2b1..19ab524342 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -18,16 +18,16 @@ use crate::utxo::{sat_from_big_decimal, utxo_common, BlockchainNetwork}; use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, DexFee, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, - RawTransactionError, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, - RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, - SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, - TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionEnum, TransactionErr, - TransactionFut, TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, UtxoStandardCoin, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, - VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, - WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest}; + RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, + RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, + SendPaymentArgs, SignRawTransactionRequest, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, + TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, + TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TxMarshalingErr, + UnexpectedDerivationMethod, UtxoStandardCoin, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, ValidateWatcherSpendInput, VerificationError, VerificationResult, + WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use bitcoin::bech32::ToBase32; use bitcoin::hashes::Hash; @@ -1028,6 +1028,7 @@ impl WatcherOps for LightningCoin { } } +#[async_trait] impl MarketCoinOps for LightningCoin { fn ticker(&self) -> &str { &self.conf.ticker } @@ -1106,6 +1107,13 @@ impl MarketCoinOps for LightningCoin { )) } + #[inline(always)] + async fn sign_raw_tx(&self, _args: &SignRawTransactionRequest) -> RawTransactionResult { + MmError::err(RawTransactionError::NotImplemented { + coin: self.ticker().to_string(), + }) + } + // Todo: Add waiting for confirmations logic for the case of if the channel is closed and the htlc can be claimed on-chain // Todo: The above is postponed and might not be needed after this issue is resolved https://github.com/lightningdevkit/rust-lightning/issues/2017 fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send> { diff --git a/mm2src/coins/lightning/ln_events.rs b/mm2src/coins/lightning/ln_events.rs index a90b13be82..d823f00f8a 100644 --- a/mm2src/coins/lightning/ln_events.rs +++ b/mm2src/coins/lightning/ln_events.rs @@ -2,6 +2,7 @@ use super::*; use crate::lightning::ln_db::{DBChannelDetails, HTLCStatus, LightningDB, PaymentType}; use crate::lightning::ln_errors::{SaveChannelClosingError, SaveChannelClosingResult}; use crate::lightning::ln_sql::SqliteLightningDB; +use crate::utxo::UtxoCommonOps; use bitcoin::blockdata::script::Script; use bitcoin::blockdata::transaction::Transaction; use bitcoin::consensus::encode::serialize_hex; @@ -219,7 +220,9 @@ fn sign_funding_transaction( .activated_key_or_err() .map_err(|e| SignFundingTransactionError::Internal(e.to_string()))?; - let prev_script = Builder::build_p2pkh(&my_address.hash); + let prev_script = coin + .script_for_address(my_address) + .map_err(|e| SignFundingTransactionError::Internal(e.to_string()))?; let signed = sign_tx( unsigned, key_pair, @@ -529,7 +532,7 @@ impl LightningEventHandler { let keys_manager = self.keys_manager.clone(); let fut = async move { - let change_destination_script = Builder::build_witness_script(&my_address.hash).to_bytes().take().into(); + let change_destination_script = Builder::build_p2witness(&my_address.hash).to_bytes().take().into(); let feerate_sat_per_1000_weight = platform.get_est_sat_per_1000_weight(ConfirmationTarget::Normal); let output_descriptors = outputs.iter().collect::>(); let claiming_tx = match keys_manager.spend_spendable_outputs( diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index ad33246977..fc6bc4d495 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -58,6 +58,7 @@ use futures::compat::Future01CompatExt; use futures::lock::Mutex as AsyncMutex; use futures::{FutureExt, TryFutureExt}; use futures01::Future; +use hex::FromHexError; use http::{Response, StatusCode}; use keys::{AddressFormat as UtxoAddressFormat, KeyPair, NetworkPrefix as CashAddrPrefix}; use mm2_core::mm_ctx::{from_ctx, MmArc}; @@ -217,6 +218,7 @@ pub mod eth; use eth::GetValidEthWithdrawAddError; use eth::{eth_coin_from_conf_and_request, get_eth_address, EthCoin, EthGasDetailsErr, EthTxFeeDetails, GetEthAddressError, SignedEthTx}; +use ethereum_types::U256; pub mod hd_confirm_address; pub mod hd_pubkey; @@ -348,17 +350,34 @@ pub enum RawTransactionError { HashNotExist(String), #[display(fmt = "Internal error: {}", _0)] InternalError(String), + #[display(fmt = "Transaction decode error: {}", _0)] + DecodeError(String), + #[display(fmt = "Invalid param: {}", _0)] + InvalidParam(String), + #[display(fmt = "Non-existent previous output: {}", _0)] + NonExistentPrevOutputError(String), + #[display(fmt = "Signing error: {}", _0)] + SigningError(String), + #[display(fmt = "Not implemented for this coin {}", coin)] + NotImplemented { coin: String }, + #[display(fmt = "Transaction error {}", _0)] + TransactionError(String), } impl HttpStatusCode for RawTransactionError { fn status_code(&self) -> StatusCode { match self { + RawTransactionError::Transport(_) + | RawTransactionError::InternalError(_) + | RawTransactionError::SigningError(_) => StatusCode::INTERNAL_SERVER_ERROR, RawTransactionError::NoSuchCoin { .. } | RawTransactionError::InvalidHashError(_) - | RawTransactionError::HashNotExist(_) => StatusCode::BAD_REQUEST, - RawTransactionError::Transport(_) | RawTransactionError::InternalError(_) => { - StatusCode::INTERNAL_SERVER_ERROR - }, + | RawTransactionError::HashNotExist(_) + | RawTransactionError::DecodeError(_) + | RawTransactionError::InvalidParam(_) + | RawTransactionError::NonExistentPrevOutputError(_) + | RawTransactionError::TransactionError(_) => StatusCode::BAD_REQUEST, + RawTransactionError::NotImplemented { .. } => StatusCode::NOT_IMPLEMENTED, } } } @@ -371,6 +390,14 @@ impl From for RawTransactionError { } } +impl From for RawTransactionError { + fn from(e: NumConversError) -> Self { RawTransactionError::InvalidParam(e.to_string()) } +} + +impl From for RawTransactionError { + fn from(e: FromHexError) -> Self { RawTransactionError::InvalidParam(e.to_string()) } +} + #[derive(Clone, Debug, Deserialize, Display, EnumFromStringify, PartialEq, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum GetMyAddressError { @@ -415,6 +442,62 @@ pub struct RawTransactionRes { pub tx_hex: BytesJson, } +/// Previous utxo transaction data for signing +#[derive(Clone, Debug, Deserialize)] +pub struct PrevTxns { + /// transaction hash + tx_hash: String, + /// transaction output index + index: u32, + /// transaction output script pub key + script_pub_key: String, + // TODO: implement if needed: + // redeem script for P2SH script pubkey + // pub redeem_script: Option, + /// transaction output amount + amount: BigDecimal, +} + +/// sign_raw_transaction RPC request's params for signing raw utxo transactions +#[derive(Clone, Debug, Deserialize)] +pub struct SignUtxoTransactionParams { + /// unsigned utxo transaction in hex + tx_hex: String, + /// optional data of previous transactions referred by unsigned transaction inputs + prev_txns: Option>, + // TODO: add if needed for utxo: + // pub sighash_type: Option, optional signature hash type, one of values: NONE, SINGLE, ALL, NONE|ANYONECANPAY, SINGLE|ANYONECANPAY, ALL|ANYONECANPAY (if not set 'ALL' is used) + // pub branch_id: Option, zcash or komodo optional consensus branch id, used for signing transactions ahead of current height +} + +/// sign_raw_transaction RPC request's params for signing raw eth transactions +#[derive(Clone, Debug, Deserialize)] +pub struct SignEthTransactionParams { + /// Eth transfer value + value: Option, + /// Eth to address + to: Option, + /// Eth contract data + data: Option, + /// Eth gas use limit + gas_limit: U256, +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(tag = "type", content = "tx")] +pub enum SignRawTransactionEnum { + UTXO(SignUtxoTransactionParams), + ETH(SignEthTransactionParams), +} + +/// sign_raw_transaction RPC request +#[derive(Clone, Debug, Deserialize)] +pub struct SignRawTransactionRequest { + coin: String, + #[serde(flatten)] + tx: SignRawTransactionEnum, +} + #[derive(Debug, Deserialize)] pub struct MyAddressReq { coin: String, @@ -1401,6 +1484,7 @@ pub trait SwapOpsV2: CoinAssocTypes + Send + Sync + 'static { /// Operations that coins have independently from the MarketMaker. /// That is, things implemented by the coin wallets or public coin services. +#[async_trait] pub trait MarketCoinOps { fn ticker(&self) -> &str; @@ -1441,6 +1525,9 @@ pub trait MarketCoinOps { /// Receives raw transaction bytes as input and returns tx hash in hexadecimal format fn send_raw_tx_bytes(&self, tx: &[u8]) -> Box + Send>; + /// Signs raw utxo transaction in hexadecimal format as input and returns signed transaction in hexadecimal format + async fn sign_raw_tx(&self, args: &SignRawTransactionRequest) -> RawTransactionResult; + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send>; fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut; @@ -3874,6 +3961,11 @@ pub async fn verify_message(ctx: MmArc, req: VerificationRequest) -> Verificatio Ok(VerificationResponse { is_valid }) } +pub async fn sign_raw_transaction(ctx: MmArc, req: SignRawTransactionRequest) -> RawTransactionResult { + let coin = lp_coinfind_or_err(&ctx, &req.coin).await?; + coin.sign_raw_tx(&req).await +} + pub async fn remove_delegation(ctx: MmArc, req: RemoveDelegateRequest) -> DelegationResult { let coin = lp_coinfind_or_err(&ctx, &req.coin).await?; match coin { diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index 4043d299bb..10869db6f8 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -13,22 +13,23 @@ use crate::utxo::utxo_builder::{UtxoCoinBuildError, UtxoCoinBuildResult, UtxoCoi use crate::utxo::utxo_common::{self, big_decimal_from_sat, check_all_utxo_inputs_signed_by_pub, UtxoTxBuilder}; use crate::utxo::{qtum, ActualTxFee, AdditionalTxData, AddrFromStrError, BroadcastTxErr, FeePolicy, GenerateTxError, GetUtxoListOps, HistoryUtxoTx, HistoryUtxoTxMap, MatureUnspentList, RecentlySpentOutPointsGuard, - UtxoActivationParams, UtxoAddressFormat, UtxoCoinFields, UtxoCommonOps, UtxoFromLegacyReqErr, - UtxoTx, UtxoTxBroadcastOps, UtxoTxGenerationOps, VerboseTransactionFrom, UTXO_LOCK}; + UnsupportedAddr, UtxoActivationParams, UtxoAddressFormat, UtxoCoinFields, UtxoCommonOps, + UtxoFromLegacyReqErr, UtxoTx, UtxoTxBroadcastOps, UtxoTxGenerationOps, VerboseTransactionFrom, + UTXO_LOCK}; use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, DexFee, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, IguanaPrivKey, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, - RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, - SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, - SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, - TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, - TransactionFut, TransactionResult, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, - WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, - WithdrawRequest, WithdrawResult}; + RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, + RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, + SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, + TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, + TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TransactionType, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, + VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, + WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use chain::TransactionOutput; @@ -88,6 +89,7 @@ pub enum Qrc20GenTxError { ErrorSigningTx(UtxoSignWithKeyPairError), PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), UnexpectedDerivationMethod(UnexpectedDerivationMethod), + InvalidAddress(String), } impl From for Qrc20GenTxError { @@ -119,6 +121,7 @@ impl Qrc20GenTxError { Qrc20GenTxError::ErrorSigningTx(sign_err) => WithdrawError::InternalError(sign_err.to_string()), Qrc20GenTxError::PrivKeyPolicyNotAllowed(priv_err) => WithdrawError::InternalError(priv_err.to_string()), Qrc20GenTxError::UnexpectedDerivationMethod(addr_err) => WithdrawError::InternalError(addr_err.to_string()), + Qrc20GenTxError::InvalidAddress(addr_err) => WithdrawError::InvalidAddress(addr_err), } } } @@ -538,7 +541,9 @@ impl Qrc20Coin { let my_address = self.utxo.derivation_method.single_addr_or_err()?; let key_pair = self.utxo.priv_key_policy.activated_key_or_err()?; - let prev_script = ScriptBuilder::build_p2pkh(&my_address.hash); + let prev_script = self + .script_for_address(my_address) + .map_err(|e| Qrc20GenTxError::InvalidAddress(e.to_string()))?; let signed = sign_tx( unsigned, key_pair, @@ -670,6 +675,10 @@ impl UtxoCommonOps for Qrc20Coin { utxo_common::checked_address_from_str(self, address) } + fn script_for_address(&self, address: &Address) -> MmResult { + utxo_common::get_script_for_address(self.as_ref(), address) + } + async fn get_current_mtp(&self) -> UtxoRpcResult { utxo_common::get_current_mtp(&self.utxo, CoinVariant::Qtum).await } @@ -1183,6 +1192,7 @@ impl WatcherOps for Qrc20Coin { } } +#[async_trait] impl MarketCoinOps for Qrc20Coin { fn ticker(&self) -> &str { &self.utxo.conf.ticker } @@ -1253,6 +1263,11 @@ impl MarketCoinOps for Qrc20Coin { utxo_common::send_raw_tx_bytes(&self.utxo, tx) } + #[inline(always)] + async fn sign_raw_tx(&self, args: &SignRawTransactionRequest) -> RawTransactionResult { + utxo_common::sign_raw_tx(self, args).await + } + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send> { let tx: UtxoTx = try_fus!(deserialize(input.payment_tx.as_slice()).map_err(|e| ERRL!("{:?}", e))); let selfi = self.clone(); diff --git a/mm2src/coins/rpc_command/lightning/open_channel.rs b/mm2src/coins/rpc_command/lightning/open_channel.rs index f79ec0b433..bcabd615a7 100644 --- a/mm2src/coins/rpc_command/lightning/open_channel.rs +++ b/mm2src/coins/rpc_command/lightning/open_channel.rs @@ -161,8 +161,7 @@ pub async fn open_channel(ctx: MmArc, req: OpenChannelRequest) -> OpenChannelRes // The actual script_pubkey will replace this before signing the transaction after receiving the required // output script from the other node when the channel is accepted - let script_pubkey = - Builder::build_witness_script(&AddressHashEnum::WitnessScriptHash(Default::default())).to_bytes(); + let script_pubkey = Builder::build_p2witness(&AddressHashEnum::WitnessScriptHash(Default::default())).to_bytes(); let outputs = vec![TransactionOutput { value, script_pubkey }]; let mut tx_builder = UtxoTxBuilder::new(&platform_coin) diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index 025f4c6c7d..766dce1ce5 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -5,15 +5,16 @@ use crate::solana::spl::SplTokenInfo; use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, DexFee, FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, - PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, - RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, - SignatureResult, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, - TradePreimageValue, TransactionDetails, TransactionFut, TransactionResult, TransactionType, - TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, - WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; + PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, RawTransactionRequest, + RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, SignatureResult, + SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, TradePreimageValue, + TransactionDetails, TransactionFut, TransactionResult, TransactionType, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherReward, WatcherRewardError, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, + WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use base58::ToBase58; use bincode::{deserialize, serialize}; @@ -378,6 +379,7 @@ impl SolanaCoin { } } +#[async_trait] impl MarketCoinOps for SolanaCoin { fn ticker(&self) -> &str { &self.ticker } @@ -441,6 +443,13 @@ impl MarketCoinOps for SolanaCoin { Box::new(fut.boxed().compat()) } + #[inline(always)] + async fn sign_raw_tx(&self, _args: &SignRawTransactionRequest) -> RawTransactionResult { + MmError::err(RawTransactionError::NotImplemented { + coin: self.ticker().to_string(), + }) + } + fn wait_for_confirmations(&self, _input: ConfirmPaymentInput) -> Box + Send> { unimplemented!() } diff --git a/mm2src/coins/solana/spl.rs b/mm2src/coins/solana/spl.rs index 0777e278e5..e93e88af93 100644 --- a/mm2src/coins/solana/spl.rs +++ b/mm2src/coins/solana/spl.rs @@ -4,11 +4,12 @@ use crate::solana::solana_common::{ui_amount_to_amount, PrepareTransferData, Suf use crate::solana::{solana_common, AccountError, SolanaCommonOps, SolanaFeeDetails}; use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, DexFee, FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, - PaymentInstructions, PaymentInstructionsErr, RawTransactionFut, RawTransactionRequest, RefundError, - RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, - SendPaymentArgs, SignatureResult, SolanaCoin, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, - TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionFut, TransactionResult, - TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + PaymentInstructions, PaymentInstructionsErr, RawTransactionError, RawTransactionFut, + RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, + SignatureResult, SolanaCoin, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, + TradePreimageValue, TransactionDetails, TransactionFut, TransactionResult, TransactionType, + TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, @@ -230,6 +231,7 @@ impl SplToken { } } +#[async_trait] impl MarketCoinOps for SplToken { fn ticker(&self) -> &str { &self.conf.ticker } @@ -266,6 +268,13 @@ impl MarketCoinOps for SplToken { self.platform_coin.send_raw_tx_bytes(tx) } + #[inline(always)] + async fn sign_raw_tx(&self, _args: &SignRawTransactionRequest) -> RawTransactionResult { + MmError::err(RawTransactionError::NotImplemented { + coin: self.ticker().to_string(), + }) + } + fn wait_for_confirmations(&self, _input: ConfirmPaymentInput) -> Box + Send> { unimplemented!() } diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 89b79573c3..18d825977e 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -19,16 +19,16 @@ use crate::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BigDecimal, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicy, PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionRes, - RefundError, RefundPaymentArgs, RefundResult, RpcCommonOps, SearchForSwapTxSpendInput, - SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, SignatureResult, SpendPaymentArgs, - SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, - TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, - TransactionResult, TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, VerificationError, - VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawError, WithdrawFee, WithdrawFrom, WithdrawFut, WithdrawRequest}; + RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, RpcCommonOps, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, + SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, + TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, + TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TransactionType, TxFeeDetails, + TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, + ValidateWatcherSpendInput, VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFrom, WithdrawFut, WithdrawRequest}; use async_std::prelude::FutureExt as AsyncStdFutureExt; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; @@ -2214,6 +2214,7 @@ impl MmCoin for TendermintCoin { fn on_token_deactivated(&self, _ticker: &str) {} } +#[async_trait] impl MarketCoinOps for TendermintCoin { fn ticker(&self) -> &str { &self.ticker } @@ -2303,6 +2304,13 @@ impl MarketCoinOps for TendermintCoin { Box::new(fut.boxed().compat()) } + #[inline(always)] + async fn sign_raw_tx(&self, _args: &SignRawTransactionRequest) -> RawTransactionResult { + MmError::err(RawTransactionError::NotImplemented { + coin: self.ticker().to_string(), + }) + } + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send> { // Sanity check let _: TxRaw = try_fus!(Message::decode(input.payment_tx.as_slice())); diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 93c5eb56f7..a508520539 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -10,16 +10,17 @@ 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, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MyAddressError, - NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, RawTransactionFut, - RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, - SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, - TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, - TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TransactionType, - TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawError, WithdrawFrom, WithdrawFut, WithdrawRequest}; + NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, RawTransactionError, + RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, + RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, + SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, + TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, + TransactionErr, TransactionFut, TransactionResult, TransactionType, TxFeeDetails, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFrom, WithdrawFut, + WithdrawRequest}; use crate::{DexFee, MmCoinEnum, PaymentInstructionArgs, ValidateWatcherSpendInput, WatcherReward, WatcherRewardError}; use async_trait::async_trait; use bitcrypto::sha256; @@ -535,6 +536,7 @@ impl WatcherOps for TendermintToken { } } +#[async_trait] impl MarketCoinOps for TendermintToken { fn ticker(&self) -> &str { &self.ticker } @@ -579,6 +581,13 @@ impl MarketCoinOps for TendermintToken { self.platform_coin.send_raw_tx_bytes(tx) } + #[inline(always)] + async fn sign_raw_tx(&self, _args: &SignRawTransactionRequest) -> RawTransactionResult { + MmError::err(RawTransactionError::NotImplemented { + coin: self.ticker().to_string(), + }) + } + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send> { self.platform_coin.wait_for_confirmations(input) } diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index d4da2109bd..714aed56c8 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -6,9 +6,9 @@ use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPay CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, - RefundFundingSecretArgs, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, - SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignatureResult, - SpendPaymentArgs, SwapOpsV2, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, + RawTransactionResult, RefundFundingSecretArgs, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignRawTransactionRequest, + SignatureResult, SpendPaymentArgs, SwapOpsV2, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionErr, TransactionResult, TxMarshalingErr, TxPreimageWithSig, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, @@ -54,6 +54,7 @@ impl TestCoin { pub fn new(ticker: &str) -> TestCoin { TestCoin(Arc::new(TestCoinImpl { ticker: ticker.into() })) } } +#[async_trait] #[mockable] impl MarketCoinOps for TestCoin { fn ticker(&self) -> &str { &self.ticker } @@ -81,6 +82,9 @@ impl MarketCoinOps for TestCoin { fn send_raw_tx_bytes(&self, tx: &[u8]) -> Box + Send> { unimplemented!() } + #[inline(always)] + async fn sign_raw_tx(&self, _args: &SignRawTransactionRequest) -> RawTransactionResult { unimplemented!() } + fn wait_for_confirmations(&self, _input: ConfirmPaymentInput) -> Box + Send> { unimplemented!() } diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index 622f2eebb5..cef1c3f3c9 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -103,10 +103,10 @@ use self::rpc_clients::{electrum_script_hash, ElectrumClient, ElectrumRpcRequest use super::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BalanceResult, CoinBalance, CoinFutSpawner, CoinsContext, DerivationMethod, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, KmdRewardsDetails, MarketCoinOps, MmCoin, NumConversError, NumConversResult, PrivKeyActivationPolicy, PrivKeyPolicy, - PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RawTransactionResult, - RpcTransportEventHandler, RpcTransportEventHandlerShared, TradeFee, TradePreimageError, TradePreimageFut, - TradePreimageResult, Transaction, TransactionDetails, TransactionEnum, TransactionErr, - UnexpectedDerivationMethod, VerificationError, WithdrawError, WithdrawRequest}; + PrivKeyPolicyNotAllowed, RawTransactionFut, RpcTransportEventHandler, RpcTransportEventHandlerShared, + TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, Transaction, TransactionDetails, + TransactionEnum, TransactionErr, UnexpectedDerivationMethod, VerificationError, WithdrawError, + WithdrawRequest}; use crate::coin_balance::{EnableCoinScanPolicy, EnabledCoinBalanceParams, HDAddressBalanceScanner}; use crate::hd_wallet::{HDAccountOps, HDAccountsMutex, HDAddress, HDAddressId, HDWalletCoinOps, HDWalletOps, InvalidBip44ChainError}; @@ -962,6 +962,9 @@ pub trait UtxoCommonOps: /// and if it failed inform user that he used a wrong format. fn address_from_str(&self, address: &str) -> MmResult; + /// For an address create corresponding utxo output script + fn script_for_address(&self, address: &Address) -> MmResult; + async fn get_current_mtp(&self) -> UtxoRpcResult; /// Check if the output is spendable (is not coinbase or it has enough confirmations). @@ -1868,7 +1871,8 @@ where _ => coin.as_ref().conf.signature_version, }; - let prev_script = Builder::build_p2pkh(&my_address.hash); + let prev_script = utxo_common::get_script_for_address(coin.as_ref(), my_address) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; let signed = try_tx_s!(sign_tx( unsigned, key_pair, @@ -1886,12 +1890,12 @@ where pub fn output_script(address: &Address, script_type: ScriptType) -> Script { match address.addr_format { - UtxoAddressFormat::Segwit => Builder::build_witness_script(&address.hash), + UtxoAddressFormat::Segwit => Builder::build_p2witness(&address.hash), _ => match script_type { ScriptType::P2PKH => Builder::build_p2pkh(&address.hash), ScriptType::P2SH => Builder::build_p2sh(&address.hash), - ScriptType::P2WPKH => Builder::build_witness_script(&address.hash), - ScriptType::P2WSH => Builder::build_witness_script(&address.hash), + ScriptType::P2WPKH => Builder::build_p2witness(&address.hash), + ScriptType::P2WSH => Builder::build_p2witness(&address.hash), }, } } diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index 072719eeee..f9c9d18b11 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -12,14 +12,15 @@ use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetails use crate::{BlockHeightAndTime, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinProtocol, CoinWithDerivationMethod, ConfirmPaymentInput, DexFee, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, - PrivKeyBuildPolicy, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, - RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, - SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, - TransactionResult, TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, - VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut}; + PrivKeyBuildPolicy, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, + RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, + SendPaymentArgs, SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapOps, + TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TransactionResult, TransactionType, TxFeeDetails, + TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawFut}; use common::executor::{AbortableSystem, AbortedError}; use common::log::warn; use derive_more::Display; @@ -758,6 +759,10 @@ impl UtxoCommonOps for BchCoin { utxo_common::checked_address_from_str(self, address) } + fn script_for_address(&self, address: &Address) -> MmResult { + utxo_common::get_script_for_address(self.as_ref(), address) + } + async fn get_current_mtp(&self) -> UtxoRpcResult { utxo_common::get_current_mtp(&self.utxo_arc, CoinVariant::Standard).await } @@ -1137,6 +1142,7 @@ impl WatcherOps for BchCoin { } } +#[async_trait] impl MarketCoinOps for BchCoin { fn ticker(&self) -> &str { &self.utxo_arc.conf.ticker } @@ -1183,6 +1189,11 @@ impl MarketCoinOps for BchCoin { utxo_common::send_raw_tx_bytes(&self.utxo_arc, tx) } + #[inline(always)] + async fn sign_raw_tx(&self, args: &SignRawTransactionRequest) -> RawTransactionResult { + utxo_common::sign_raw_tx(self, args).await + } + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send> { utxo_common::wait_for_confirmations(&self.utxo_arc, input) } diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index a66ae04331..ba12a39f0a 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -27,13 +27,14 @@ use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetails use crate::{eth, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, DelegationError, DelegationFut, DexFee, GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, - PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, RefundResult, - SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, - SpendPaymentArgs, StakingInfosFut, SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, - TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, - WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + PaymentInstructionsErr, PrivKeyBuildPolicy, RawTransactionRequest, RawTransactionResult, RefundError, + RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, + SendPaymentArgs, SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, StakingInfosFut, SwapOps, + TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TransactionResult, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, + WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; use common::executor::{AbortableSystem, AbortedError}; use crypto::Bip44Chain; @@ -418,6 +419,10 @@ impl UtxoCommonOps for QtumCoin { utxo_common::checked_address_from_str(self, address) } + fn script_for_address(&self, address: &Address) -> MmResult { + utxo_common::get_script_for_address(self.as_ref(), address) + } + async fn get_current_mtp(&self) -> UtxoRpcResult { utxo_common::get_current_mtp(&self.utxo_arc, CoinVariant::Qtum).await } @@ -811,6 +816,7 @@ impl WatcherOps for QtumCoin { } } +#[async_trait] impl MarketCoinOps for QtumCoin { fn ticker(&self) -> &str { &self.utxo_arc.conf.ticker } @@ -849,6 +855,11 @@ impl MarketCoinOps for QtumCoin { utxo_common::send_raw_tx_bytes(&self.utxo_arc, tx) } + #[inline(always)] + async fn sign_raw_tx(&self, args: &SignRawTransactionRequest) -> RawTransactionResult { + utxo_common::sign_raw_tx(self, args).await + } + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send> { utxo_common::wait_for_confirmations(&self.utxo_arc, input) } diff --git a/mm2src/coins/utxo/qtum_delegation.rs b/mm2src/coins/utxo/qtum_delegation.rs index 231d230c51..e602dbcc5b 100644 --- a/mm2src/coins/utxo/qtum_delegation.rs +++ b/mm2src/coins/utxo/qtum_delegation.rs @@ -20,7 +20,6 @@ use keys::{AddressHashEnum, Signature}; use mm2_err_handle::prelude::*; use mm2_number::bigdecimal::{BigDecimal, Zero}; use rpc::v1::types::ToTxHash; -use script::Builder as ScriptBuilder; use serialization::serialize; use std::convert::TryInto; use std::str::FromStr; @@ -290,7 +289,9 @@ impl QtumCoin { DelegationError::from_generate_tx_error(gen_tx_error, self.ticker().to_string(), utxo.decimals) })?; - let prev_script = ScriptBuilder::build_p2pkh(&my_address.hash); + let prev_script = self + .script_for_address(my_address) + .map_err(|e| DelegationError::InternalError(e.to_string()))?; let signed = sign_tx( unsigned, key_pair, diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index da2a51224b..9899b58c7c 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -16,16 +16,17 @@ use crate::utxo::{generate_and_send_tx, sat_from_big_decimal, ActualTxFee, Addit use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, DexFee, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, NegotiateSwapContractAddrErr, NumConversError, PaymentInstructionArgs, PaymentInstructions, - PaymentInstructionsErr, PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RefundError, - RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, - SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, - TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, - TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TxFeeDetails, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentInput, ValidateWatcherSpendInput, VerificationError, - VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest}; + PaymentInstructionsErr, PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, + RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, SignatureResult, + SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, + TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, + TransactionFut, TransactionResult, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, + ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, + ValidatePaymentInput, ValidateWatcherSpendInput, VerificationError, VerificationResult, + WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, + WithdrawRequest}; use async_trait::async_trait; use bitcrypto::dhash160; use chain::constants::SEQUENCE_FINAL; @@ -1084,6 +1085,7 @@ impl UtxoTxGenerationOps for SlpToken { } } +#[async_trait] impl MarketCoinOps for SlpToken { fn ticker(&self) -> &str { &self.conf.ticker } @@ -1163,6 +1165,11 @@ impl MarketCoinOps for SlpToken { Box::new(fut.boxed().compat()) } + #[inline(always)] + async fn sign_raw_tx(&self, args: &SignRawTransactionRequest) -> RawTransactionResult { + utxo_common::sign_raw_tx(self, args).await + } + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send> { self.platform_coin.wait_for_confirmations(input) } @@ -1669,7 +1676,10 @@ impl MmCoin for SlpToken { WithdrawError::from_generate_tx_error(gen_tx_error, coin.platform_ticker().into(), platform_decimals) })?; - let prev_script = ScriptBuilder::build_p2pkh(&my_address.hash); + let prev_script = coin + .platform_coin + .script_for_address(my_address) + .map_err(|e| WithdrawError::InvalidAddress(e.to_string()))?; let signed = sign_tx( unsigned, key_pair, diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 2354d8ba44..6cc4f1b36f 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -17,12 +17,13 @@ use crate::utxo::utxo_withdraw::{InitUtxoWithdraw, StandardUtxoWithdraw, UtxoWit use crate::watcher_common::validate_watcher_reward; use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, DexFee, GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, HDAccountAddressId, - RawTransactionError, RawTransactionRequest, RawTransactionRes, RefundFundingSecretArgs, RefundPaymentArgs, - RewardTarget, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, - SendTakerFundingArgs, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TradePreimageValue, - TransactionFut, TransactionResult, TxFeeDetails, TxGenError, TxMarshalingErr, TxPreimageWithSig, - ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, - ValidateTakerFundingArgs, ValidateTakerFundingError, ValidateTakerFundingResult, + RawTransactionError, RawTransactionRequest, RawTransactionRes, RawTransactionResult, + RefundFundingSecretArgs, RefundPaymentArgs, RewardTarget, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignRawTransactionEnum, + SignRawTransactionRequest, SignUtxoTransactionParams, SignatureError, SignatureResult, SpendPaymentArgs, + SwapOps, TradePreimageValue, TransactionFut, TransactionResult, TxFeeDetails, TxGenError, TxMarshalingErr, + TxPreimageWithSig, ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, + ValidatePaymentInput, ValidateTakerFundingArgs, ValidateTakerFundingError, ValidateTakerFundingResult, ValidateTakerFundingSpendPreimageError, ValidateTakerFundingSpendPreimageResult, ValidateTakerPaymentSpendPreimageError, ValidateTakerPaymentSpendPreimageResult, ValidateWatcherSpendInput, VerificationError, VerificationResult, WatcherSearchForSwapTxSpendInput, @@ -33,10 +34,10 @@ use crate::{MmCoinEnum, WatcherReward, WatcherRewardError}; pub use bitcrypto::{dhash160, sha256, ChecksumType}; use bitcrypto::{dhash256, ripemd160}; use chain::constants::SEQUENCE_FINAL; -use chain::{OutPoint, TransactionOutput}; +use chain::{OutPoint, TransactionInput, TransactionOutput}; use common::executor::Timer; use common::jsonrpc_client::JsonRpcErrorType; -use common::log::{error, warn}; +use common::log::{debug, error, warn}; use crypto::{Bip32DerPathOps, Bip44Chain, RpcDerivationPath, StandardHDPath, StandardHDPathError}; use futures::compat::Future01CompatExt; use futures::future::{FutureExt, TryFutureExt}; @@ -755,6 +756,35 @@ pub fn tx_size_in_v_bytes(from_addr_format: &UtxoAddressFormat, tx: &UtxoTx) -> } } +/// Implements building utxo script pubkey for an address by the address format +pub fn get_script_for_address(coin: &UtxoCoinFields, addr: &Address) -> MmResult { + match addr.addr_format { + UtxoAddressFormat::Standard => { + if addr.prefix == coin.conf.pub_addr_prefix && addr.t_addr_prefix == coin.conf.pub_t_addr_prefix { + Ok(Builder::build_p2pkh(&addr.hash)) + } else if addr.prefix == coin.conf.p2sh_addr_prefix && addr.t_addr_prefix == coin.conf.p2sh_t_addr_prefix { + Ok(Builder::build_p2sh(&addr.hash)) + } else { + MmError::err(UnsupportedAddr::PrefixError(coin.conf.ticker.clone())) + } + }, + UtxoAddressFormat::Segwit => Ok(Builder::build_p2witness(&addr.hash)), + UtxoAddressFormat::CashAddress { + network: _, + pub_addr_prefix, + p2sh_addr_prefix, + } => { + if pub_addr_prefix == coin.conf.pub_addr_prefix { + Ok(Builder::build_p2pkh(&addr.hash)) + } else if p2sh_addr_prefix == coin.conf.p2sh_addr_prefix { + Ok(Builder::build_p2sh(&addr.hash)) + } else { + MmError::err(UnsupportedAddr::PrefixError(coin.conf.ticker.clone())) + } + }, + } +} + pub struct UtxoTxBuilder<'a, T: AsRef + UtxoTxGenerationOps> { coin: &'a T, from: Option
, @@ -920,7 +950,7 @@ impl<'a, T: AsRef + UtxoTxGenerationOps> UtxoTxBuilder<'a, T> { } /// Generates unsigned transaction (TransactionInputSigner) from specified utxos and outputs. - /// Sends the change (inputs amount - outputs amount) to the [`UtxoTxBuilder::from`] address. + /// sends the change (inputs amount - outputs amount) to the [`UtxoTxBuilder::from`] address. /// Also returns additional transaction data pub async fn build(mut self) -> GenerateTxResult { let coin = self.coin; @@ -1037,6 +1067,47 @@ impl<'a, T: AsRef + UtxoTxGenerationOps> UtxoTxBuilder<'a, T> { .calc_interest_if_required(self.tx, data, change_script_pubkey, dust) .await?) } + + /// Generates unsigned transaction (TransactionInputSigner) from specified utxos and outputs. + /// Adds or updates inputs with UnspentInfo + /// Does not do any checks or add any outputs + pub async fn build_unchecked(mut self) -> Result> { + for output in self.tx.outputs.iter() { + self.sum_outputs_value += output.value; + } + + true_or!( + !self.available_inputs.is_empty() || !self.tx.inputs.is_empty(), + GenerateTxError::EmptyUtxoSet { + required: self.sum_outputs_value + } + ); + + for utxo in self.available_inputs.clone() { + if let Some(input) = self + .tx + .inputs + .iter_mut() + .find(|input| input.previous_output == utxo.outpoint) + { + input.amount = utxo.value; + } else { + self.tx.inputs.push(UnsignedTransactionInput { + previous_output: utxo.outpoint, + sequence: SEQUENCE_FINAL, + amount: utxo.value, + witness: vec![], + }); + } + } + + Ok(self.tx) + } + + pub fn with_transaction_input_signer(mut self, tx_input_signer: TransactionInputSigner) -> Self { + self.tx = tx_input_signer; + self + } } /// Calculates interest if the coin is KMD @@ -2902,6 +2973,189 @@ pub fn send_raw_tx_bytes( ) } +/// Helper to load unspent outputs from cache or rpc +/// also returns first previous scriptpubkey +async fn get_unspents_for_inputs( + coin: &UtxoCoinFields, + inputs: &Vec, +) -> Result<(Option