diff --git a/Cargo.lock b/Cargo.lock index d9d267da8e..5ef455aa48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7587,6 +7587,7 @@ dependencies = [ "subsystem", "thiserror", "tokio", + "wallet-types", ] [[package]] @@ -8107,7 +8108,7 @@ dependencies = [ [[package]] name = "trezor-client" version = "0.1.3" -source = "git+https://github.com/mintlayer/mintlayer-trezor-firmware?branch=feature/mintlayer#aaf3552da31a9446e868c4fafb9ee740afac0c48" +source = "git+https://github.com/mintlayer/mintlayer-trezor-firmware?branch=feature/mintlayer#653a62d73675b2d74f5412da61e752bbd73b2ce4" dependencies = [ "bitcoin", "byteorder", @@ -8715,6 +8716,7 @@ dependencies = [ "mempool-types", "node-comm", "p2p-types", + "pos-accounting", "randomness", "rpc-description", "rstest", @@ -8877,6 +8879,7 @@ dependencies = [ "storage", "test-utils", "thiserror", + "tx-verifier", "utils", "zeroize", ] diff --git a/build-tools/codecheck/codecheck.py b/build-tools/codecheck/codecheck.py index 4f4cac4514..a372b7aa90 100755 --- a/build-tools/codecheck/codecheck.py +++ b/build-tools/codecheck/codecheck.py @@ -267,7 +267,7 @@ def check_local_licenses(): template = re.compile('(?:' + r')\n(?:'.join(LICENSE_TEMPLATE) + ')') ok = True - for path in rs_sources(['wallet/trezor-client']): + for path in rs_sources(): if any(fnmatch.fnmatch(os.path.abspath(path), os.path.abspath(exempted)) for exempted in exempted_files): continue diff --git a/chainstate/src/rpc/mod.rs b/chainstate/src/rpc/mod.rs index 63324daafc..9d038c84f2 100644 --- a/chainstate/src/rpc/mod.rs +++ b/chainstate/src/rpc/mod.rs @@ -31,7 +31,7 @@ use common::{ address::{dehexify::to_dehexified_json, Address}, chain::{ tokens::{RPCTokenInfo, TokenId}, - ChainConfig, DelegationId, PoolId, TxOutput, + ChainConfig, DelegationId, Destination, PoolId, TxOutput, }, primitives::{Amount, BlockHeight, Id}, }; @@ -142,6 +142,15 @@ trait ChainstateRpc { #[method(name = "staker_balance")] async fn staker_balance(&self, pool_address: String) -> RpcResult>; + /// Returns the pool's decommission destination associated with the given pool address. + /// + /// Returns `None` (null) if the pool is not found. + #[method(name = "pool_decommission_destination")] + async fn pool_decommission_destination( + &self, + pool_address: String, + ) -> RpcResult>; + /// Given a pool defined by a pool address, and a delegation address, /// returns the amount of coins owned by that delegation in that pool. #[method(name = "delegation_share")] @@ -332,6 +341,25 @@ impl ChainstateRpcServer for super::ChainstateHandle { ) } + async fn pool_decommission_destination( + &self, + pool_address: String, + ) -> RpcResult> { + rpc::handle_result( + self.call(move |this| { + let chain_config = this.get_chain_config(); + let result: Result, _> = + dynamize_err(Address::::from_string(chain_config, pool_address)) + .map(|address| address.into_object()) + .and_then(|pool_id| dynamize_err(this.get_stake_pool_data(pool_id))) + .map(|pool_data| pool_data.map(|d| d.decommission_destination().clone())); + + result + }) + .await, + ) + } + async fn delegation_share( &self, pool_address: String, diff --git a/chainstate/tx-verifier/src/transaction_verifier/input_check/signature_only_check.rs b/chainstate/tx-verifier/src/transaction_verifier/input_check/signature_only_check.rs index 537c6b6d5a..1f4c96c0fc 100644 --- a/chainstate/tx-verifier/src/transaction_verifier/input_check/signature_only_check.rs +++ b/chainstate/tx-verifier/src/transaction_verifier/input_check/signature_only_check.rs @@ -16,7 +16,6 @@ use std::convert::Infallible; use common::chain::{ - partially_signed_transaction::PartiallySignedTransaction, signature::{inputsig::InputWitness, DestinationSigError, Transactable}, tokens::TokenId, ChainConfig, DelegationId, Destination, PoolId, SignedTransaction, TxInput, TxOutput, @@ -102,7 +101,6 @@ impl InputInfoProvider for InputVerifyContextSignature<'_, T> { // Prevent BlockRewardTransactable from being used here pub trait SignatureOnlyVerifiable {} impl SignatureOnlyVerifiable for SignedTransaction {} -impl SignatureOnlyVerifiable for PartiallySignedTransaction {} pub fn verify_tx_signature( chain_config: &ChainConfig, diff --git a/common/src/chain/tokens/rpc.rs b/common/src/chain/tokens/rpc.rs index b704404ee9..1ed5495c99 100644 --- a/common/src/chain/tokens/rpc.rs +++ b/common/src/chain/tokens/rpc.rs @@ -51,6 +51,13 @@ impl RPCTokenInfo { Self::NonFungibleToken(_) => 0, } } + + pub fn token_ticker(&self) -> &[u8] { + match self { + Self::FungibleToken(info) => info.token_ticker.as_bytes(), + Self::NonFungibleToken(info) => info.metadata.ticker.as_bytes(), + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, HasValueHint)] diff --git a/common/src/chain/transaction/mod.rs b/common/src/chain/transaction/mod.rs index 750759fd64..4d979bb51e 100644 --- a/common/src/chain/transaction/mod.rs +++ b/common/src/chain/transaction/mod.rs @@ -13,7 +13,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use partially_signed_transaction::PartiallySignedTransaction; use thiserror::Error; use serialization::{DirectDecode, DirectEncode}; @@ -34,7 +33,6 @@ pub use account_nonce::*; pub mod utxo_outpoint; pub use utxo_outpoint::*; -pub mod partially_signed_transaction; pub mod signed_transaction; pub mod output; @@ -102,14 +100,6 @@ impl Eq for WithId {} pub enum TransactionCreationError { #[error("The number of signatures does not match the number of inputs")] InvalidWitnessCount, - #[error("The number of input utxos does not match the number of inputs")] - InvalidInputUtxosCount, - #[error("The number of destinations does not match the number of inputs")] - InvalidDestinationsCount, - #[error("The number of htlc secrets does not match the number of inputs")] - InvalidHtlcSecretsCount, - #[error("Failed to convert partially signed tx to signed")] - FailedToConvertPartiallySignedTx(PartiallySignedTransaction), } impl Transaction { diff --git a/crypto/src/key/extended.rs b/crypto/src/key/extended.rs index 34d67905ad..2caac2bc9b 100644 --- a/crypto/src/key/extended.rs +++ b/crypto/src/key/extended.rs @@ -122,7 +122,7 @@ impl ExtendedPublicKey { } } - pub fn from_hardware_public_key(public_key: Secp256k1ExtendedPublicKey) -> Self { + pub fn new(public_key: Secp256k1ExtendedPublicKey) -> Self { Self { pub_key: ExtendedPublicKeyHolder::Secp256k1Schnorr(public_key), } diff --git a/crypto/src/key/secp256k1/extended_keys.rs b/crypto/src/key/secp256k1/extended_keys.rs index d639f44f34..e41186ebf1 100644 --- a/crypto/src/key/secp256k1/extended_keys.rs +++ b/crypto/src/key/secp256k1/extended_keys.rs @@ -201,7 +201,7 @@ impl Secp256k1ExtendedPublicKey { } } - pub fn from_hardware_wallet( + pub fn new( derivation_path: DerivationPath, chain_code: ChainCode, public_key: Secp256k1PublicKey, diff --git a/node-daemon/docs/RPC.md b/node-daemon/docs/RPC.md index 88623f19ed..30853b1e61 100644 --- a/node-daemon/docs/RPC.md +++ b/node-daemon/docs/RPC.md @@ -347,6 +347,25 @@ EITHER OF 2) null ``` +### Method `chainstate_pool_decommission_destination` + +Returns the pool's decommission destination associated with the given pool address. + +Returns `None` (null) if the pool is not found. + + +Parameters: +``` +{ "pool_address": string } +``` + +Returns: +``` +EITHER OF + 1) bech32 string + 2) null +``` + ### Method `chainstate_delegation_share` Given a pool defined by a pool address, and a delegation address, diff --git a/rpc/types/src/string.rs b/rpc/types/src/string.rs index c93ec4ee40..e609b49977 100644 --- a/rpc/types/src/string.rs +++ b/rpc/types/src/string.rs @@ -112,6 +112,10 @@ impl RpcString { self.0 } + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + pub fn try_into_string(self) -> Result { String::from_utf8(self.0).map_err(|e| { let err = e.utf8_error(); diff --git a/test-rpc-functions/Cargo.toml b/test-rpc-functions/Cargo.toml index 608b31801c..731751a3b5 100644 --- a/test-rpc-functions/Cargo.toml +++ b/test-rpc-functions/Cargo.toml @@ -16,6 +16,7 @@ randomness = { path = "../randomness/" } rpc = { path = "../rpc/" } serialization = { path = "../serialization" } subsystem = { path = "../subsystem/" } +wallet-types = { path = "../wallet/types" } async-trait.workspace = true futures.workspace = true diff --git a/test-rpc-functions/src/rpc.rs b/test-rpc-functions/src/rpc.rs index fcfa88fd8f..368eecd988 100644 --- a/test-rpc-functions/src/rpc.rs +++ b/test-rpc-functions/src/rpc.rs @@ -28,7 +28,6 @@ use common::{ EpochIndex, }, output_value::OutputValue, - partially_signed_transaction::PartiallySignedTransaction, signature::inputsig::{ authorize_hashed_timelock_contract_spend::AuthorizedHashedTimelockContractSpend, InputWitness, @@ -44,6 +43,7 @@ use serialization::{ hex::{HexDecode, HexEncode}, hex_encoded::HexEncoded, }; +use wallet_types::partially_signed_transaction::PartiallySignedTransaction; use crate::{RpcTestFunctionsError, RpcTestFunctionsHandle}; diff --git a/test/functional/test_framework/__init__.py b/test/functional/test_framework/__init__.py index f0cbe81db0..398f60cee3 100644 --- a/test/functional/test_framework/__init__.py +++ b/test/functional/test_framework/__init__.py @@ -198,6 +198,31 @@ def init_mintlayer_types(): ], }, + "TokenAdditionalInfo": { + "type": "struct", + "type_mapping": [ + ["num_decimals", "u8"], + ["ticker", "Vec"], + ] + }, + + "UtxoAdditionalInfo": { + "type": "enum", + "type_mapping": [ + ["TokenInfo", "TokenAdditionalInfo"], + ["PoolInfo", "(Amount)"], + ["AnyoneCanTake", ""], # TODO + ], + }, + + "UtxoWithAdditionalInfo": { + "type": "struct", + "type_mapping": [ + ["utxo", "TxOutput"], + ["additional_info", "Option"], + ] + }, + "StandardInputSignature": { "type": "struct", "type_mapping": [ @@ -219,9 +244,10 @@ def init_mintlayer_types(): "type_mapping": [ ["tx", "TransactionV1"], ["witnesses", "Vec>"], - ["input_utxos", "Vec>"], + ["input_utxos", "Vec>"], ["destinations", "Vec>"], ["htlc_secrets", "Vec>"], + ["output_additional_infos", "Vec>"], ] }, diff --git a/test/functional/wallet_htlc_refund.py b/test/functional/wallet_htlc_refund.py index 9c8931fb31..8cf4309d71 100644 --- a/test/functional/wallet_htlc_refund.py +++ b/test/functional/wallet_htlc_refund.py @@ -132,7 +132,9 @@ async def async_test(self): assert_not_in("Tokens", balance) # issue a valid token - token_id, _ = (await wallet.issue_new_token("XXXX", 2, "http://uri", alice_address)) + token_ticker = "XXXX" + token_number_of_decimals = 2 + token_id, _ = (await wallet.issue_new_token(token_ticker, token_number_of_decimals, "http://uri", alice_address)) assert token_id is not None self.log.info(f"new token id: {token_id}") token_id_hex = node.test_functions_reveal_token_id(token_id) @@ -182,9 +184,10 @@ async def async_test(self): alice_refund_ptx = { 'tx': tx['transaction'], 'witnesses': [None, None], - 'input_utxos': alice_htlc_outputs, + 'input_utxos': [{'utxo': out, 'additional_info': None} for out in alice_htlc_outputs], 'destinations': [refund_dest_obj, alice_htlc_change_dest], - 'htlc_secrets': [None, None] + 'htlc_secrets': [None, None], + 'output_additional_infos': [None] } alice_refund_tx_hex = scalecodec.base.RuntimeConfiguration().create_scale_object('PartiallySignedTransaction').encode(alice_refund_ptx).to_hex()[2:] @@ -210,9 +213,10 @@ async def async_test(self): bob_refund_ptx = { 'tx': tx['transaction'], 'witnesses': [None, None], - 'input_utxos': bob_htlc_outputs, + 'input_utxos': [{'utxo': out, 'additional_info': None} for out in bob_htlc_outputs], 'destinations': [refund_dest_obj, bob_htlc_change_dest], - 'htlc_secrets': [None, None] + 'htlc_secrets': [None, None], + 'output_additional_infos': [None] } bob_refund_tx_hex = scalecodec.base.RuntimeConfiguration().create_scale_object('PartiallySignedTransaction').encode(bob_refund_ptx).to_hex()[2:] diff --git a/wallet/src/account/currency_grouper/mod.rs b/wallet/src/account/currency_grouper/mod.rs index 550712d15f..499fbe86c9 100644 --- a/wallet/src/account/currency_grouper/mod.rs +++ b/wallet/src/account/currency_grouper/mod.rs @@ -30,7 +30,7 @@ pub enum Currency { Token(TokenId), } -pub(crate) fn group_outputs( +pub fn group_outputs( outputs: impl Iterator, get_tx_output: impl Fn(&T) -> &TxOutput, mut combiner: impl FnMut(&mut Grouped, &T, Amount) -> WalletResult<()>, diff --git a/wallet/src/account/mod.rs b/wallet/src/account/mod.rs index 9ff35727f5..a671e58e86 100644 --- a/wallet/src/account/mod.rs +++ b/wallet/src/account/mod.rs @@ -22,7 +22,6 @@ use common::address::pubkeyhash::PublicKeyHash; use common::chain::block::timestamp::BlockTimestamp; use common::chain::classic_multisig::ClassicMultisigChallenge; use common::chain::htlc::HashedTimelockContract; -use common::chain::partially_signed_transaction::PartiallySignedTransaction; use common::chain::{AccountCommand, AccountOutPoint, AccountSpending}; use common::primitives::id::WithId; use common::primitives::{Idable, H256}; @@ -38,6 +37,7 @@ use utils::ensure; pub use utxo_selector::UtxoSelectorError; use wallet_types::account_id::AccountPrefixedId; use wallet_types::account_info::{StandaloneAddressDetails, StandaloneAddresses}; +use wallet_types::partially_signed_transaction::{PartiallySignedTransaction, UtxoAdditionalInfo}; use wallet_types::with_locked::WithLocked; use crate::account::utxo_selector::{select_coins, OutputGroup}; @@ -46,7 +46,8 @@ use crate::key_chain::{AccountKeyChains, KeyChainError, VRFAccountKeyChains}; use crate::send_request::{ make_address_output, make_address_output_from_delegation, make_address_output_token, make_decommission_stake_pool_output, make_mint_token_outputs, make_stake_output, - make_unmint_token_outputs, IssueNftArguments, SelectedInputs, StakePoolDataArguments, + make_unmint_token_outputs, IssueNftArguments, PoolOrTokenId, SelectedInputs, + StakePoolDataArguments, }; use crate::wallet::WalletPoolsFilter; use crate::wallet_events::{WalletEvents, WalletEventsNoOp}; @@ -84,7 +85,7 @@ use wallet_types::{ use self::currency_grouper::Currency; pub use self::output_cache::{ - DelegationData, FungibleTokenInfo, PoolData, TxInfo, UnconfirmedTokenInfo, UtxoWithTxOutput, + DelegationData, OwnFungibleTokenInfo, PoolData, TxInfo, UnconfirmedTokenInfo, UtxoWithTxOutput, }; use self::output_cache::{OutputCache, TokenIssuanceData}; use self::transaction_list::{get_transaction_list, TransactionList}; @@ -256,10 +257,7 @@ impl Account { ) } SelectedInputs::Inputs(ref inputs) => ( - inputs - .iter() - .map(|(outpoint, utxo)| (outpoint.clone(), (utxo, None))) - .collect(), + inputs.iter().map(|(outpoint, utxo)| (outpoint.clone(), utxo)).collect(), selection_algo, ), } @@ -427,7 +425,7 @@ impl Account { &self, fee_rates: CurrentFeeRate, pay_fee_with_currency: ¤cy_grouper::Currency, - utxos: Vec<(UtxoOutPoint, (&TxOutput, Option))>, + utxos: Vec<(UtxoOutPoint, &TxOutput)>, ) -> Result>, WalletError> { let utxo_to_output_group = |(outpoint, txo): (UtxoOutPoint, TxOutput)| -> WalletResult { @@ -455,9 +453,9 @@ impl Account { currency_grouper::group_utxos_for_input( utxos.into_iter(), - |(_, (tx_output, _))| tx_output, + |(_, tx_output)| tx_output, |grouped: &mut Vec<(UtxoOutPoint, TxOutput)>, element, _| -> WalletResult<()> { - grouped.push((element.0.clone(), element.1 .0.clone())); + grouped.push((element.0.clone(), element.1.clone())); Ok(()) }, vec![], @@ -614,6 +612,7 @@ impl Account { change_addresses: BTreeMap>, median_time: BlockTimestamp, fee_rate: CurrentFeeRate, + additional_utxo_infos: &BTreeMap, ) -> WalletResult<(PartiallySignedTransaction, BTreeMap)> { let mut request = self.select_inputs_for_send_request( request, @@ -626,7 +625,7 @@ impl Account { )?; let fees = request.get_fees(); - let ptx = request.into_partially_signed_tx()?; + let ptx = request.into_partially_signed_tx(additional_utxo_infos)?; Ok((ptx, fees)) } @@ -861,7 +860,7 @@ impl Account { pub fn get_token_unconfirmed_info( &self, - token_info: &RPCFungibleTokenInfo, + token_info: RPCFungibleTokenInfo, ) -> WalletResult { self.output_cache .get_token_unconfirmed_info(token_info, |destination: &Destination| { @@ -960,7 +959,7 @@ impl Account { median_time: BlockTimestamp, fee_rate: CurrentFeeRate, ) -> WalletResult { - let token_id = *token_info.token_id(); + let token_id = token_info.token_id(); let outputs = make_mint_token_outputs(token_id, amount, address); token_info.check_can_mint(amount)?; @@ -987,7 +986,7 @@ impl Account { median_time: BlockTimestamp, fee_rate: CurrentFeeRate, ) -> WalletResult { - let token_id = *token_info.token_id(); + let token_id = token_info.token_id(); let outputs = make_unmint_token_outputs(token_id, amount); token_info.check_can_unmint(amount)?; @@ -1013,7 +1012,7 @@ impl Account { median_time: BlockTimestamp, fee_rate: CurrentFeeRate, ) -> WalletResult { - let token_id = *token_info.token_id(); + let token_id = token_info.token_id(); token_info.check_can_lock()?; let nonce = token_info.get_next_nonce()?; @@ -1043,7 +1042,7 @@ impl Account { let nonce = token_info.get_next_nonce()?; let tx_input = TxInput::AccountCommand( nonce, - AccountCommand::FreezeToken(*token_info.token_id(), is_token_unfreezable), + AccountCommand::FreezeToken(token_info.token_id(), is_token_unfreezable), ); let authority = token_info.authority()?.clone(); @@ -1068,7 +1067,7 @@ impl Account { let nonce = token_info.get_next_nonce()?; let tx_input = - TxInput::AccountCommand(nonce, AccountCommand::UnfreezeToken(*token_info.token_id())); + TxInput::AccountCommand(nonce, AccountCommand::UnfreezeToken(token_info.token_id())); let authority = token_info.authority()?.clone(); self.change_token_supply_transaction( @@ -1094,7 +1093,7 @@ impl Account { let nonce = token_info.get_next_nonce()?; let tx_input = TxInput::AccountCommand( nonce, - AccountCommand::ChangeTokenAuthority(*token_info.token_id(), new_authority), + AccountCommand::ChangeTokenAuthority(token_info.token_id(), new_authority), ); let authority = token_info.authority()?.clone(); @@ -1136,67 +1135,38 @@ impl Account { self.output_cache.pool_data(pool_id).is_ok() } - pub fn tx_to_partially_signed_tx( + pub fn find_account_destination( &self, - tx: Transaction, - median_time: BlockTimestamp, - ) -> WalletResult { - let current_block_info = BlockInfo { - height: self.account_info.best_block_height(), - timestamp: median_time, - }; + acc_outpoint: &AccountOutPoint, + ) -> WalletResult { + match acc_outpoint.account() { + AccountSpending::DelegationBalance(delegation_id, _) => self + .output_cache + .delegation_data(delegation_id) + .map(|data| data.destination.clone()) + .ok_or(WalletError::DelegationNotFound(*delegation_id)), + } + } - let (input_utxos, destinations) = tx - .inputs() - .iter() - .map(|tx_inp| match tx_inp { - TxInput::Utxo(outpoint) => { - // find utxo from cache - self.find_unspent_utxo_with_destination(outpoint, current_block_info) - .map(|(out, dest)| (Some(out), Some(dest))) - } - TxInput::Account(acc_outpoint) => { - // find delegation destination - match acc_outpoint.account() { - AccountSpending::DelegationBalance(delegation_id, _) => self - .output_cache - .delegation_data(delegation_id) - .map(|data| (None, Some(data.destination.clone()))) - .ok_or(WalletError::DelegationNotFound(*delegation_id)), - } - } - TxInput::AccountCommand(_, cmd) => { - // find authority of the token - match cmd { - AccountCommand::MintTokens(token_id, _) - | AccountCommand::UnmintTokens(token_id) - | AccountCommand::LockTokenSupply(token_id) - | AccountCommand::ChangeTokenAuthority(token_id, _) - | AccountCommand::FreezeToken(token_id, _) - | AccountCommand::UnfreezeToken(token_id) => self - .output_cache - .token_data(token_id) - .map(|data| (None, Some(data.authority.clone()))) - .ok_or(WalletError::UnknownTokenId(*token_id)), - // TODO(orders) - AccountCommand::ConcludeOrder(_) => unimplemented!(), - AccountCommand::FillOrder(_, _, _) => unimplemented!(), - } - } - }) - .collect::>>()? - .into_iter() - .unzip(); - - let num_inputs = tx.inputs().len(); - let ptx = PartiallySignedTransaction::new( - tx, - vec![None; num_inputs], - input_utxos, - destinations, - None, - )?; - Ok(ptx) + pub fn find_account_command_destination( + &self, + cmd: &AccountCommand, + ) -> WalletResult { + match cmd { + AccountCommand::MintTokens(token_id, _) + | AccountCommand::UnmintTokens(token_id) + | AccountCommand::LockTokenSupply(token_id) + | AccountCommand::ChangeTokenAuthority(token_id, _) + | AccountCommand::FreezeToken(token_id, _) + | AccountCommand::UnfreezeToken(token_id) => self + .output_cache + .token_data(token_id) + .map(|data| data.authority.clone()) + .ok_or(WalletError::UnknownTokenId(*token_id)), + // TODO(orders) + AccountCommand::ConcludeOrder(_) => unimplemented!(), + AccountCommand::FillOrder(_, _, _) => unimplemented!(), + } } pub fn find_unspent_utxo_with_destination( @@ -1204,8 +1174,7 @@ impl Account { outpoint: &UtxoOutPoint, current_block_info: BlockInfo, ) -> WalletResult<(TxOutput, Destination)> { - let (txo, _) = - self.output_cache.find_unspent_unlocked_utxo(outpoint, current_block_info)?; + let txo = self.output_cache.find_unspent_unlocked_utxo(outpoint, current_block_info)?; Ok(( txo.clone(), @@ -1324,14 +1293,14 @@ impl Account { }; let amounts_by_currency = currency_grouper::group_utxos_for_input( self.output_cache - .utxos_with_token_ids( + .utxos( current_block_info, UtxoState::Confirmed.into(), WithLocked::Unlocked, |txo| get_utxo_type(txo).is_some() && self.is_watched_by(txo, &address), ) .into_iter(), - |(_, (tx_output, _))| tx_output, + |(_, tx_output)| tx_output, |total: &mut Amount, _, amount| -> WalletResult<()> { *total = (*total + amount).ok_or(WalletError::OutputAmountOverflow)?; Ok(()) @@ -1516,7 +1485,7 @@ impl Account { with_locked, ) .into_iter(), - |(_, (tx_output, _))| tx_output, + |(_, tx_output)| tx_output, |total: &mut Amount, _, amount| -> WalletResult<()> { *total = (*total + amount).ok_or(WalletError::OutputAmountOverflow)?; Ok(()) @@ -1532,20 +1501,15 @@ impl Account { median_time: BlockTimestamp, utxo_states: UtxoStates, with_locked: WithLocked, - ) -> Vec<(UtxoOutPoint, (&TxOutput, Option))> { + ) -> Vec<(UtxoOutPoint, &TxOutput)> { let current_block_info = BlockInfo { height: self.account_info.best_block_height(), timestamp: median_time, }; - self.output_cache.utxos_with_token_ids( - current_block_info, - utxo_states, - with_locked, - |txo| { - self.is_watched_multisig_output(txo) - && get_utxo_type(txo).is_some_and(|v| utxo_types.contains(v)) - }, - ) + self.output_cache.utxos(current_block_info, utxo_states, with_locked, |txo| { + self.is_watched_multisig_output(txo) + && get_utxo_type(txo).is_some_and(|v| utxo_types.contains(v)) + }) } pub fn get_utxos( @@ -1554,17 +1518,14 @@ impl Account { median_time: BlockTimestamp, utxo_states: UtxoStates, with_locked: WithLocked, - ) -> Vec<(UtxoOutPoint, (&TxOutput, Option))> { + ) -> Vec<(UtxoOutPoint, &TxOutput)> { let current_block_info = BlockInfo { height: self.account_info.best_block_height(), timestamp: median_time, }; - self.output_cache.utxos_with_token_ids( - current_block_info, - utxo_states, - with_locked, - |txo| self.is_mine(txo) && get_utxo_type(txo).is_some_and(|v| utxo_types.contains(v)), - ) + self.output_cache.utxos(current_block_info, utxo_states, with_locked, |txo| { + self.is_mine(txo) && get_utxo_type(txo).is_some_and(|v| utxo_types.contains(v)) + }) } pub fn get_transaction_list(&self, skip: usize, count: usize) -> WalletResult { diff --git a/wallet/src/account/output_cache/mod.rs b/wallet/src/account/output_cache/mod.rs index 929abc7ccc..55da36f82e 100644 --- a/wallet/src/account/output_cache/mod.rs +++ b/wallet/src/account/output_cache/mod.rs @@ -25,8 +25,8 @@ use common::{ output_value::OutputValue, stakelock::StakePoolData, tokens::{ - is_token_or_nft_issuance, make_token_id, IsTokenFreezable, IsTokenUnfreezable, - RPCFungibleTokenInfo, RPCIsTokenFrozen, RPCTokenTotalSupply, TokenId, TokenIssuance, + make_token_id, IsTokenFreezable, IsTokenUnfreezable, RPCFungibleTokenInfo, + RPCIsTokenFrozen, RPCNonFungibleTokenInfo, RPCTokenTotalSupply, TokenId, TokenIssuance, TokenTotalSupply, }, AccountCommand, AccountNonce, AccountSpending, DelegationId, Destination, GenBlock, @@ -47,9 +47,12 @@ use wallet_types::{ AccountWalletTxId, BlockInfo, WalletTx, }; -use crate::{destination_getters::get_all_tx_output_destinations, WalletError, WalletResult}; +use crate::{ + destination_getters::get_all_tx_output_destinations, send_request::get_referenced_token_ids, + WalletError, WalletResult, +}; -pub type UtxoWithTxOutput<'a> = (UtxoOutPoint, (&'a TxOutput, Option)); +pub type UtxoWithTxOutput<'a> = (UtxoOutPoint, &'a TxOutput); #[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize, HasValueHint)] pub struct TxInfo { @@ -258,33 +261,83 @@ impl TokenCurrentSupplyState { } } -pub struct FungibleTokenInfo { +pub struct OwnFungibleTokenInfo { frozen: TokenFreezableState, last_nonce: Option, total_supply: TokenCurrentSupplyState, authority: Destination, + num_decimals: u8, + ticker: Vec, +} + +pub struct FungibleTokenInfo { + frozen: TokenFreezableState, + num_decimals: u8, + ticker: Vec, +} + +impl From for FungibleTokenInfo { + fn from(value: RPCFungibleTokenInfo) -> Self { + Self { + frozen: value.frozen.into(), + num_decimals: value.number_of_decimals, + ticker: value.token_ticker.into_bytes(), + } + } +} + +pub struct NonFungibleTokenInfo { + ticker: Vec, +} + +impl From<&RPCNonFungibleTokenInfo> for NonFungibleTokenInfo { + fn from(value: &RPCNonFungibleTokenInfo) -> Self { + Self { + ticker: value.metadata.ticker.as_bytes().to_vec(), + } + } } +/// Token info from the Node + any unconfirmed Txs from this wallet pub enum UnconfirmedTokenInfo { - OwnFungibleToken(TokenId, FungibleTokenInfo), - FungibleToken(TokenId, TokenFreezableState), - NonFungibleToken(TokenId), + /// Token info owned by this wallet + OwnFungibleToken(TokenId, OwnFungibleTokenInfo), + /// Token info not owned by this wallet + FungibleToken(TokenId, FungibleTokenInfo), + /// NFT info + NonFungibleToken(TokenId, NonFungibleTokenInfo), } impl UnconfirmedTokenInfo { - pub fn token_id(&self) -> &TokenId { + pub fn token_id(&self) -> TokenId { match self { Self::OwnFungibleToken(token_id, _) | Self::FungibleToken(token_id, _) - | Self::NonFungibleToken(token_id) => token_id, + | Self::NonFungibleToken(token_id, _) => *token_id, + } + } + + pub fn num_decimals(&self) -> u8 { + match self { + Self::OwnFungibleToken(_, info) => info.num_decimals, + Self::FungibleToken(_, info) => info.num_decimals, + Self::NonFungibleToken(_, _) => 0, + } + } + + pub fn token_ticker(&self) -> &[u8] { + match self { + Self::OwnFungibleToken(_, info) => &info.ticker, + Self::FungibleToken(_, info) => &info.ticker, + Self::NonFungibleToken(_, info) => &info.ticker, } } pub fn check_can_be_used(&self) -> WalletResult<()> { match self { Self::OwnFungibleToken(_, state) => state.frozen.check_can_be_used(), - Self::FungibleToken(_, state) => state.check_can_be_used(), - Self::NonFungibleToken(_) => Ok(()), + Self::FungibleToken(_, state) => state.frozen.check_can_be_used(), + Self::NonFungibleToken(_, _) => Ok(()), } } @@ -294,7 +347,7 @@ impl UnconfirmedTokenInfo { Self::FungibleToken(token_id, _) => { Err(WalletError::CannotChangeNotOwnedToken(*token_id)) } - Self::NonFungibleToken(token_id) => { + Self::NonFungibleToken(token_id, _) => { Err(WalletError::CannotChangeNonFungibleToken(*token_id)) } } @@ -306,7 +359,7 @@ impl UnconfirmedTokenInfo { Self::FungibleToken(token_id, _) => { Err(WalletError::CannotChangeNotOwnedToken(*token_id)) } - Self::NonFungibleToken(token_id) => { + Self::NonFungibleToken(token_id, _) => { Err(WalletError::CannotChangeNonFungibleToken(*token_id)) } } @@ -321,7 +374,7 @@ impl UnconfirmedTokenInfo { Self::FungibleToken(token_id, _) => { Err(WalletError::CannotChangeNotOwnedToken(*token_id)) } - Self::NonFungibleToken(token_id) => { + Self::NonFungibleToken(token_id, _) => { Err(WalletError::CannotChangeNonFungibleToken(*token_id)) } } @@ -333,7 +386,7 @@ impl UnconfirmedTokenInfo { Self::FungibleToken(token_id, _) => { Err(WalletError::CannotChangeNotOwnedToken(*token_id)) } - Self::NonFungibleToken(token_id) => { + Self::NonFungibleToken(token_id, _) => { Err(WalletError::CannotChangeNonFungibleToken(*token_id)) } } @@ -345,7 +398,7 @@ impl UnconfirmedTokenInfo { Self::FungibleToken(token_id, _) => { Err(WalletError::CannotChangeNotOwnedToken(*token_id)) } - Self::NonFungibleToken(token_id) => { + Self::NonFungibleToken(token_id, _) => { Err(WalletError::CannotChangeNonFungibleToken(*token_id)) } } @@ -357,7 +410,7 @@ impl UnconfirmedTokenInfo { Self::FungibleToken(token_id, _) => { Err(WalletError::CannotChangeNotOwnedToken(*token_id)) } - Self::NonFungibleToken(token_id) => { + Self::NonFungibleToken(token_id, _) => { Err(WalletError::CannotChangeNonFungibleToken(*token_id)) } } @@ -369,7 +422,7 @@ impl UnconfirmedTokenInfo { Self::FungibleToken(token_id, _) => { Err(WalletError::CannotChangeNotOwnedToken(*token_id)) } - Self::NonFungibleToken(token_id) => { + Self::NonFungibleToken(token_id, _) => { Err(WalletError::CannotChangeNonFungibleToken(*token_id)) } } @@ -380,7 +433,7 @@ impl UnconfirmedTokenInfo { match self { Self::OwnFungibleToken(_, state) => Some(state.total_supply.current_supply()), Self::FungibleToken(_, _) => None, - Self::NonFungibleToken(_) => None, + Self::NonFungibleToken(_, _) => None, } } } @@ -596,7 +649,7 @@ impl OutputCache { pub fn get_token_unconfirmed_info bool>( &self, - token_info: &RPCFungibleTokenInfo, + token_info: RPCFungibleTokenInfo, is_mine: F, ) -> WalletResult { let token_data = match self.token_issuance.get(&token_info.token_id) { @@ -604,7 +657,7 @@ impl OutputCache { if !is_mine(&token_data.authority) { return Ok(UnconfirmedTokenInfo::FungibleToken( token_info.token_id, - token_info.frozen.into(), + token_info.into(), )); } token_data @@ -613,7 +666,7 @@ impl OutputCache { None => { return Ok(UnconfirmedTokenInfo::FungibleToken( token_info.token_id, - token_info.frozen.into(), + token_info.into(), )); } }; @@ -640,11 +693,13 @@ impl OutputCache { Ok(UnconfirmedTokenInfo::OwnFungibleToken( token_info.token_id, - FungibleTokenInfo { + OwnFungibleTokenInfo { frozen: frozen_state, last_nonce: token_data.last_nonce, total_supply, authority: token_data.authority.clone(), + num_decimals: token_info.number_of_decimals, + ticker: token_info.token_ticker.into_bytes(), }, )) } @@ -1076,7 +1131,7 @@ impl OutputCache { &self, utxo: &UtxoOutPoint, current_block_info: BlockInfo, - ) -> WalletResult<(&TxOutput, Option)> { + ) -> WalletResult<&TxOutput> { let tx = self .txs .get(&utxo.source_id()) @@ -1111,14 +1166,7 @@ impl OutputCache { WalletError::TokenV0Utxo(utxo.clone()) ); - let token_id = match tx { - WalletTx::Tx(tx_data) => is_token_or_nft_issuance(output) - .then_some(make_token_id(tx_data.get_transaction().inputs())) - .flatten(), - WalletTx::Block(_) => None, - }; - - Ok((output, token_id)) + Ok(output) } pub fn find_used_tokens( @@ -1126,14 +1174,14 @@ impl OutputCache { current_block_info: BlockInfo, inputs: &[UtxoOutPoint], ) -> WalletResult> { - inputs - .iter() - .filter_map(|utxo| { - self.find_unspent_unlocked_utxo(utxo, current_block_info) - .map(|(_, token_id)| token_id) - .transpose() - }) - .collect() + inputs.iter().try_fold(BTreeSet::new(), |mut token_ids, utxo| { + let new_ids = self + .find_unspent_unlocked_utxo(utxo, current_block_info) + .map(get_referenced_token_ids)?; + token_ids.extend(new_ids); + + Ok(token_ids) + }) } pub fn find_utxos( @@ -1150,13 +1198,13 @@ impl OutputCache { .collect() } - pub fn utxos_with_token_ids bool>( + pub fn utxos bool>( &self, current_block_info: BlockInfo, utxo_states: UtxoStates, locked_state: WithLocked, output_filter: F, - ) -> Vec<(UtxoOutPoint, (&TxOutput, Option))> { + ) -> Vec<(UtxoOutPoint, &TxOutput)> { let output_filter = &output_filter; self.txs .values() @@ -1180,15 +1228,7 @@ impl OutputCache { && !is_v0_token_output(output) && output_filter(output) }) - .map(move |(output, outpoint)| { - let token_id = match tx { - WalletTx::Tx(tx_data) => is_token_or_nft_issuance(output) - .then_some(make_token_id(tx_data.get_transaction().inputs())) - .flatten(), - WalletTx::Block(_) => None, - }; - (outpoint, (output, token_id)) - }) + .map(|(output, outpoint)| (outpoint, output)) }) .collect() } diff --git a/wallet/src/send_request/mod.rs b/wallet/src/send_request/mod.rs index bf7d299032..f10be71854 100644 --- a/wallet/src/send_request/mod.rs +++ b/wallet/src/send_request/mod.rs @@ -13,15 +13,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use std::mem::take; use common::address::Address; use common::chain::output_value::OutputValue; -use common::chain::partially_signed_transaction::PartiallySignedTransaction; use common::chain::stakelock::StakePoolData; use common::chain::timelock::OutputTimeLock::ForBlockCount; -use common::chain::tokens::{Metadata, TokenId, TokenIssuance}; +use common::chain::tokens::{Metadata, NftIssuance, TokenId, TokenIssuance}; use common::chain::{ ChainConfig, Destination, PoolId, Transaction, TxInput, TxOutput, UtxoOutPoint, }; @@ -29,12 +28,21 @@ use common::primitives::per_thousand::PerThousand; use common::primitives::{Amount, BlockHeight}; use crypto::vrf::VRFPublicKey; use utils::ensure; +use wallet_types::partially_signed_transaction::{ + PartiallySignedTransaction, TokenAdditionalInfo, UtxoAdditionalInfo, UtxoWithAdditionalInfo, +}; use crate::account::currency_grouper::Currency; use crate::account::PoolData; use crate::destination_getters::{get_tx_output_destination, HtlcSpendingCondition}; use crate::{WalletError, WalletResult}; +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum PoolOrTokenId { + PoolId(PoolId), + TokenId(TokenId), +} + /// The `SendRequest` struct provides the necessary information to the wallet /// on the precise method of sending funds to a designated destination. #[derive(Debug, Clone)] @@ -284,22 +292,134 @@ impl SendRequest { take(&mut self.fees) } - pub fn into_partially_signed_tx(self) -> WalletResult { + pub fn into_partially_signed_tx( + self, + additional_info: &BTreeMap, + ) -> WalletResult { let num_inputs = self.inputs.len(); - let tx = Transaction::new(self.flags, self.inputs, self.outputs)?; let destinations = self.destinations.into_iter().map(Some).collect(); + let utxos = self + .utxos + .into_iter() + .map(|utxo| { + utxo.map(|utxo| { + let additional_info = find_additional_info(&utxo, additional_info)?; + Ok(UtxoWithAdditionalInfo::new(utxo, additional_info)) + }) + .transpose() + }) + .collect::>>()?; + let output_additional_infos = self + .outputs + .iter() + .map(|utxo| find_additional_info(utxo, additional_info)) + .collect::>>()?; + let tx = Transaction::new(self.flags, self.inputs, self.outputs)?; let ptx = PartiallySignedTransaction::new( tx, vec![None; num_inputs], - self.utxos, + utxos, destinations, None, + output_additional_infos, )?; Ok(ptx) } } +/// Find aditional data for TxOutput, mainly for UI purposes +fn find_additional_info( + utxo: &TxOutput, + additional_info: &BTreeMap, +) -> Result, WalletError> { + let additional_info = match utxo { + TxOutput::Burn(value) + | TxOutput::Htlc(value, _) + | TxOutput::Transfer(value, _) + | TxOutput::LockThenTransfer(value, _, _) => { + find_token_additional_info(value, additional_info)?.map(UtxoAdditionalInfo::TokenInfo) + } + TxOutput::AnyoneCanTake(data) => { + let ask = find_token_additional_info(data.ask(), additional_info)?; + let give = find_token_additional_info(data.give(), additional_info)?; + + Some(UtxoAdditionalInfo::AnyoneCanTake { ask, give }) + } + TxOutput::IssueNft(_, data, _) => { + Some(UtxoAdditionalInfo::TokenInfo(TokenAdditionalInfo { + num_decimals: 0, + ticker: match data.as_ref() { + NftIssuance::V0(data) => data.metadata.ticker().clone(), + }, + })) + } + TxOutput::CreateStakePool(_, data) => Some(UtxoAdditionalInfo::PoolInfo { + staker_balance: data.pledge(), + }), + TxOutput::ProduceBlockFromStake(_, pool_id) => additional_info + .get(&PoolOrTokenId::PoolId(*pool_id)) + .ok_or(WalletError::MissingPoolAdditionalData(*pool_id)) + .map(Some)? + .cloned(), + TxOutput::DataDeposit(_) + | TxOutput::IssueFungibleToken(_) + | TxOutput::DelegateStaking(_, _) + | TxOutput::CreateDelegationId(_, _) => None, + }; + Ok(additional_info) +} + +fn find_token_additional_info( + value: &OutputValue, + additional_info: &BTreeMap, +) -> WalletResult> { + match value { + OutputValue::Coin(_) | OutputValue::TokenV0(_) => Ok(None), + OutputValue::TokenV1(token_id, _) => additional_info + .get(&PoolOrTokenId::TokenId(*token_id)) + .ok_or(WalletError::MissingTokenAdditionalData(*token_id)) + .cloned() + .map(|data| match data { + UtxoAdditionalInfo::TokenInfo(data) => Ok(Some(data.clone())), + UtxoAdditionalInfo::PoolInfo { staker_balance: _ } + | UtxoAdditionalInfo::AnyoneCanTake { ask: _, give: _ } => { + Err(WalletError::MissmatchedTokenAdditionalData(*token_id)) + } + })?, + } +} + +/// Get any referenced token by this output +/// ignore tokens V0 +pub fn get_referenced_token_ids(output: &TxOutput) -> BTreeSet { + match output { + TxOutput::Transfer(v, _) + | TxOutput::LockThenTransfer(v, _, _) + | TxOutput::Burn(v) + | TxOutput::Htlc(v, _) => referenced_token_id(v), + | TxOutput::AnyoneCanTake(data) => { + let mut tokens = referenced_token_id(data.ask()); + tokens.extend(referenced_token_id(data.give())); + tokens + } + TxOutput::CreateStakePool(_, _) + | TxOutput::ProduceBlockFromStake(_, _) + | TxOutput::CreateDelegationId(_, _) + | TxOutput::DelegateStaking(_, _) + | TxOutput::DataDeposit(_) + | TxOutput::IssueFungibleToken(_) => BTreeSet::new(), + TxOutput::IssueNft(token_id, _, _) => BTreeSet::from_iter([*token_id]), + } +} + +fn referenced_token_id(v: &OutputValue) -> BTreeSet { + match v { + OutputValue::Coin(_) | OutputValue::TokenV0(_) => BTreeSet::new(), + OutputValue::TokenV1(token_id, _) => BTreeSet::from_iter([*token_id]), + } +} + pub enum SelectedInputs { Utxos(Vec), Inputs(Vec<(UtxoOutPoint, TxOutput)>), diff --git a/wallet/src/signer/mod.rs b/wallet/src/signer/mod.rs index 81bbe06a97..ecdf76349c 100644 --- a/wallet/src/signer/mod.rs +++ b/wallet/src/signer/mod.rs @@ -16,7 +16,6 @@ use std::sync::Arc; use common::chain::{ - partially_signed_transaction::PartiallySignedTransaction, signature::{ inputsig::{ arbitrary_message::{ArbitraryMessageSignature, SignArbitraryMessageError}, @@ -30,7 +29,10 @@ use crypto::key::hdkd::{derivable::DerivationError, u31::U31}; use wallet_storage::{ WalletStorageReadLocked, WalletStorageReadUnlocked, WalletStorageWriteUnlocked, }; -use wallet_types::{signature_status::SignatureStatus, AccountId}; +use wallet_types::{ + partially_signed_transaction::PartiallySignedTransaction, signature_status::SignatureStatus, + AccountId, +}; use crate::{ key_chain::{AccountKeyChains, KeyChainError}, @@ -72,6 +74,10 @@ pub enum SignerError { MissingDestinationInTransaction, #[error("Partially signed tx is missing UTXO type input's UTXO")] MissingUtxo, + #[error("Partially signed tx is missing extra info for UTXO")] + MissingUtxoExtraInfo, + #[error("Tokens V0 are not supported")] + UnsupportedTokensV0, } type SignerResult = Result; diff --git a/wallet/src/signer/software_signer/mod.rs b/wallet/src/signer/software_signer/mod.rs index 3b8537010d..c1252b9268 100644 --- a/wallet/src/signer/software_signer/mod.rs +++ b/wallet/src/signer/software_signer/mod.rs @@ -17,7 +17,6 @@ use std::sync::Arc; use common::chain::{ htlc::HtlcSecret, - partially_signed_transaction::PartiallySignedTransaction, signature::{ inputsig::{ arbitrary_message::ArbitraryMessageSignature, @@ -48,7 +47,10 @@ use wallet_storage::{ StoreTxRwUnlocked, WalletStorageReadLocked, WalletStorageReadUnlocked, WalletStorageWriteUnlocked, }; -use wallet_types::{seed_phrase::StoreSeedPhrase, signature_status::SignatureStatus, AccountId}; +use wallet_types::{ + partially_signed_transaction::PartiallySignedTransaction, seed_phrase::StoreSeedPhrase, + signature_status::SignatureStatus, AccountId, +}; use crate::{ key_chain::{ @@ -262,7 +264,8 @@ impl Signer for SoftwareSigner { Vec, Vec, )> { - let inputs_utxo_refs: Vec<_> = ptx.input_utxos().iter().map(|u| u.as_ref()).collect(); + let inputs_utxo_refs: Vec<_> = + ptx.input_utxos().iter().map(|u| u.as_ref().map(|x| &x.utxo)).collect(); let (witnesses, prev_statuses, new_statuses) = ptx .witnesses() diff --git a/wallet/src/signer/software_signer/tests.rs b/wallet/src/signer/software_signer/tests.rs index c2ee84b145..08855044dc 100644 --- a/wallet/src/signer/software_signer/tests.rs +++ b/wallet/src/signer/software_signer/tests.rs @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::BTreeMap; use std::ops::{Add, Div, Mul, Sub}; use super::*; @@ -167,7 +168,7 @@ fn sign_transaction(#[case] seed: Seed) { let tx = Transaction::new(0, inputs, outputs).unwrap(); let req = SendRequest::from_transaction(tx, utxos.clone(), &|_| None).unwrap(); - let ptx = req.into_partially_signed_tx().unwrap(); + let ptx = req.into_partially_signed_tx(&BTreeMap::default()).unwrap(); let mut signer = SoftwareSigner::new(config.clone(), DEFAULT_ACCOUNT_INDEX); let (ptx, _, _) = signer.sign_tx(ptx, account.key_chain(), &db_tx).unwrap(); diff --git a/wallet/src/signer/trezor_signer/mod.rs b/wallet/src/signer/trezor_signer/mod.rs index 4571f7548e..29a0f6b4f8 100644 --- a/wallet/src/signer/trezor_signer/mod.rs +++ b/wallet/src/signer/trezor_signer/mod.rs @@ -21,11 +21,12 @@ use std::{ use common::{ address::Address, chain::{ + htlc::HtlcSecret, output_value::OutputValue, - partially_signed_transaction::PartiallySignedTransaction, signature::{ inputsig::{ arbitrary_message::ArbitraryMessageSignature, + authorize_hashed_timelock_contract_spend::AuthorizedHashedTimelockContractSpend, authorize_pubkey_spend::AuthorizedPublicKeySpend, authorize_pubkeyhash_spend::AuthorizedPublicKeyHashSpend, classical_multisig::{ @@ -63,12 +64,11 @@ use trezor_client::{ MintlayerDelegateStakingTxOutput, MintlayerFreezeToken, MintlayerIssueFungibleTokenTxOutput, MintlayerIssueNftTxOutput, MintlayerLockThenTransferTxOutput, MintlayerLockTokenSupply, MintlayerMintTokens, - MintlayerOutputValue, MintlayerProduceBlockFromStakeTxOutput, MintlayerTokenTotalSupply, - MintlayerTokenTotalSupplyType, MintlayerTxInput, MintlayerTxOutput, MintlayerUnfreezeToken, - MintlayerUnmintTokens, MintlayerUtxoType, + MintlayerOutputValue, MintlayerProduceBlockFromStakeTxOutput, MintlayerTokenOutputValue, + MintlayerTokenTotalSupply, MintlayerTokenTotalSupplyType, MintlayerTxInput, + MintlayerTxOutput, MintlayerUnfreezeToken, MintlayerUnmintTokens, MintlayerUtxoType, }, }; -#[allow(clippy::all)] use trezor_client::{ protos::{MintlayerTransferTxOutput, MintlayerUtxoTxInput}, Trezor, @@ -78,7 +78,12 @@ use wallet_storage::{ WalletStorageReadLocked, WalletStorageReadUnlocked, WalletStorageWriteUnlocked, }; use wallet_types::{ - account_info::DEFAULT_ACCOUNT_INDEX, signature_status::SignatureStatus, AccountId, + account_info::DEFAULT_ACCOUNT_INDEX, + partially_signed_transaction::{ + PartiallySignedTransaction, TokenAdditionalInfo, UtxoAdditionalInfo, UtxoWithAdditionalInfo, + }, + signature_status::SignatureStatus, + AccountId, }; use crate::{ @@ -112,12 +117,29 @@ impl TrezorSigner { fn make_signature( &self, - signature: &Vec, + signature: &[MintlayerSignature], destination: &Destination, sighash_type: SigHashType, sighash: H256, + secret: Option, key_chain: &impl AccountKeyChains, ) -> SignerResult<(Option, SignatureStatus)> { + let add_secret = |sig: StandardInputSignature| { + let sig = if let Some(htlc_secret) = secret { + let sig_with_secret = AuthorizedHashedTimelockContractSpend::Secret( + htlc_secret, + sig.raw_signature().to_owned(), + ); + let serialized_sig = sig_with_secret.encode(); + + StandardInputSignature::new(sig.sighash_type(), serialized_sig) + } else { + sig + }; + + InputWitness::Standard(sig) + }; + match destination { Destination::AnyoneCanSpend => Ok(( Some(InputWitness::NoSignature(None)), @@ -131,10 +153,7 @@ impl TrezorSigner { signature.insert(0, 0); let sig = Signature::from_data(signature)?; let sig = AuthorizedPublicKeyHashSpend::new(pk, sig); - let sig = InputWitness::Standard(StandardInputSignature::new( - sighash_type, - sig.encode(), - )); + let sig = add_secret(StandardInputSignature::new(sighash_type, sig.encode())); Ok((Some(sig), SignatureStatus::FullySigned)) } else { @@ -147,10 +166,7 @@ impl TrezorSigner { signature.insert(0, 0); let sig = Signature::from_data(signature)?; let sig = AuthorizedPublicKeySpend::new(sig); - let sig = InputWitness::Standard(StandardInputSignature::new( - sighash_type, - sig.encode(), - )); + let sig = add_secret(StandardInputSignature::new(sighash_type, sig.encode())); Ok((Some(sig), SignatureStatus::FullySigned)) } else { @@ -196,7 +212,7 @@ impl TrezorSigner { } }; - let sig = InputWitness::Standard(StandardInputSignature::new( + let sig = add_secret(StandardInputSignature::new( sighash_type, current_signatures.encode(), )); @@ -209,12 +225,18 @@ impl TrezorSigner { } } - fn to_trezor_output_msgs(&self, ptx: &PartiallySignedTransaction) -> Vec { + fn to_trezor_output_msgs( + &self, + ptx: &PartiallySignedTransaction, + ) -> SignerResult> { let outputs = ptx .tx() .outputs() .iter() - .map(|out| to_trezor_output_msg(&self.chain_config, out)) + .zip(ptx.output_additional_infos()) + .map(|(out, additional_info)| { + to_trezor_output_msg(&self.chain_config, out, additional_info) + }) .collect(); outputs } @@ -232,8 +254,8 @@ impl Signer for TrezorSigner { Vec, )> { let inputs = to_trezor_input_msgs(&ptx, key_chain, &self.chain_config)?; - let outputs = self.to_trezor_output_msgs(&ptx); - let utxos = to_trezor_utxo_msgs(&ptx, &self.chain_config); + let outputs = self.to_trezor_output_msgs(&ptx)?; + let utxos = to_trezor_utxo_msgs(&ptx, &self.chain_config)?; let new_signatures = self .client @@ -242,7 +264,8 @@ impl Signer for TrezorSigner { .mintlayer_sign_tx(inputs, outputs, utxos) .map_err(TrezorError::DeviceError)?; - let inputs_utxo_refs: Vec<_> = ptx.input_utxos().iter().map(|u| u.as_ref()).collect(); + let inputs_utxo_refs: Vec<_> = + ptx.input_utxos().iter().map(|u| u.as_ref().map(|x| &x.utxo)).collect(); let (witnesses, prev_statuses, new_statuses) = ptx .witnesses() @@ -258,12 +281,13 @@ impl Signer for TrezorSigner { )), InputWitness::Standard(sig) => match destination { Some(destination) => { - let sighash = - signature_hash(sig.sighash_type(), ptx.tx(), &inputs_utxo_refs, i)?; - - if sig - .verify_signature(&self.chain_config, destination, &sighash) - .is_ok() + if tx_verifier::input_check::signature_only_check::verify_tx_signature( + &self.chain_config, + destination, + &ptx, + &inputs_utxo_refs, + i, + ).is_ok() { Ok(( Some(w.clone()), @@ -271,6 +295,9 @@ impl Signer for TrezorSigner { SignatureStatus::FullySigned, )) } else if let Destination::ClassicMultisig(_) = destination { + let sighash = + signature_hash(sig.sighash_type(), ptx.tx(), &inputs_utxo_refs, i)?; + let mut current_signatures = AuthorizedClassicalMultisigSpend::from_data( sig.raw_signature(), )?; @@ -353,6 +380,9 @@ impl Signer for TrezorSigner { }, None => match (destination, new_signatures.get(i)) { (Some(destination), Some(sig)) => { + // TODO: when HTLC PR is merged get secret from the ptx + let secret = None; + let sighash_type = SigHashType::try_from(SigHashType::ALL).expect("Should not fail"); let sighash = signature_hash(sighash_type, ptx.tx(), &inputs_utxo_refs, i)?; @@ -361,6 +391,7 @@ impl Signer for TrezorSigner { destination, sighash_type, sighash, + secret, key_chain, )?; Ok((sig, SignatureStatus::NotSigned, status)) @@ -451,8 +482,8 @@ fn tx_output_value(out: &TxOutput) -> OutputValue { TxOutput::IssueNft(token_id, _, _) => { OutputValue::TokenV1(*token_id, Amount::from_atoms(1)) } - TxOutput::CreateStakePool(_, _) - | TxOutput::CreateDelegationId(_, _) + TxOutput::CreateStakePool(_, data) => OutputValue::Coin(data.pledge()), + TxOutput::CreateDelegationId(_, _) | TxOutput::ProduceBlockFromStake(_, _) | TxOutput::IssueFungibleToken(_) | TxOutput::AnyoneCanTake(_) @@ -471,13 +502,9 @@ fn to_trezor_input_msgs( .zip(ptx.input_utxos()) .zip(ptx.destinations()) .map(|((inp, utxo), dest)| match (inp, utxo, dest) { - (TxInput::Utxo(outpoint), Some(utxo), Some(dest)) => Ok(to_trezor_utxo_input( - outpoint, - utxo, - chain_config, - dest, - key_chain, - )), + (TxInput::Utxo(outpoint), Some(utxo), Some(dest)) => { + to_trezor_utxo_input(outpoint, utxo, chain_config, dest, key_chain) + } (TxInput::Account(outpoint), _, Some(dest)) => Ok(to_trezor_account_input( chain_config, dest, @@ -616,11 +643,11 @@ fn to_trezor_account_input( fn to_trezor_utxo_input( outpoint: &common::chain::UtxoOutPoint, - utxo: &TxOutput, + utxo: &UtxoWithAdditionalInfo, chain_config: &ChainConfig, dest: &Destination, key_chain: &impl AccountKeyChains, -) -> MintlayerTxInput { +) -> SignerResult { let mut inp_req = MintlayerUtxoTxInput::new(); let id = match outpoint.source_id() { OutPointSourceId::Transaction(id) => { @@ -634,22 +661,13 @@ fn to_trezor_utxo_input( }; inp_req.set_prev_hash(id.to_vec()); inp_req.set_prev_index(outpoint.output_index()); - match tx_output_value(utxo) { - OutputValue::Coin(amount) => { - let mut value = MintlayerOutputValue::new(); - value.set_amount(amount.into_atoms().to_be_bytes().to_vec()); - inp_req.value = Some(value).into(); - } - OutputValue::TokenV1(token_id, amount) => { - let mut value = MintlayerOutputValue::new(); - value.set_amount(amount.into_atoms().to_be_bytes().to_vec()); - value.set_token_id(token_id.to_hash().as_bytes().to_vec()); - inp_req.value = Some(value).into(); - } - OutputValue::TokenV0(_) => { - panic!("token v0 unsuported"); - } - } + + let output_value = tx_output_value(&utxo.utxo); + inp_req.value = Some(to_trezor_output_value( + &output_value, + &utxo.additional_info, + )?) + .into(); inp_req .set_address(Address::new(chain_config, dest.clone()).expect("addressable").into_string()); @@ -701,61 +719,81 @@ fn to_trezor_utxo_input( let mut inp = MintlayerTxInput::new(); inp.utxo = Some(inp_req).into(); - inp + Ok(inp) } -fn to_trezor_utxo_msgs( - ptx: &PartiallySignedTransaction, - chain_config: &ChainConfig, -) -> BTreeMap<[u8; 32], BTreeMap> { - let utxos = ptx.input_utxos().iter().zip(ptx.tx().inputs()).fold( - BTreeMap::new(), - |mut map: BTreeMap<[u8; 32], BTreeMap>, (utxo, inp)| { - match (inp, utxo) { - (TxInput::Utxo(outpoint), Some(utxo)) => { - let id = match outpoint.source_id() { - OutPointSourceId::Transaction(id) => id.to_hash().0, - OutPointSourceId::BlockReward(id) => id.to_hash().0, - }; - let out = to_trezor_output_msg(chain_config, utxo); - map.entry(id).or_default().insert(outpoint.output_index(), out); - } - (TxInput::Utxo(_), None) => unimplemented!("missing utxo"), - (TxInput::Account(_) | TxInput::AccountCommand(_, _), Some(_)) => { - panic!("can't have accounts as UTXOs") - } - (TxInput::Account(_) | TxInput::AccountCommand(_, _), _) => {} - } - map - }, - ); - utxos -} - -fn set_value(value: &OutputValue, out_req: &mut MintlayerTransferTxOutput) { - match value { +fn to_trezor_output_value( + output_value: &OutputValue, + additional_info: &Option, +) -> SignerResult { + let value = match output_value { OutputValue::Coin(amount) => { let mut value = MintlayerOutputValue::new(); value.set_amount(amount.into_atoms().to_be_bytes().to_vec()); - out_req.value = Some(value).into(); + value } OutputValue::TokenV1(token_id, amount) => { let mut value = MintlayerOutputValue::new(); value.set_amount(amount.into_atoms().to_be_bytes().to_vec()); - value.set_token_id(token_id.to_hash().as_bytes().to_vec()); - out_req.value = Some(value).into(); - } - OutputValue::TokenV0(_) => { - panic!("token v0 unsuported"); + let mut token_value = MintlayerTokenOutputValue::new(); + token_value.set_token_id(token_id.to_hash().as_bytes().to_vec()); + match additional_info { + Some(UtxoAdditionalInfo::TokenInfo(TokenAdditionalInfo { + num_decimals, + ticker, + })) => { + token_value.set_number_of_decimals(*num_decimals as u32); + token_value.set_token_ticker(ticker.clone()); + } + Some( + UtxoAdditionalInfo::PoolInfo { staker_balance: _ } + | UtxoAdditionalInfo::AnyoneCanTake { ask: _, give: _ }, + ) + | None => return Err(SignerError::MissingUtxoExtraInfo), + } + value.token = Some(token_value).into(); + value } + OutputValue::TokenV0(_) => return Err(SignerError::UnsupportedTokensV0), }; + Ok(value) } -fn to_trezor_output_msg(chain_config: &ChainConfig, out: &TxOutput) -> MintlayerTxOutput { - match out { +fn to_trezor_utxo_msgs( + ptx: &PartiallySignedTransaction, + chain_config: &ChainConfig, +) -> SignerResult>> { + let mut utxos: BTreeMap<[u8; 32], BTreeMap> = BTreeMap::new(); + for (utxo, inp) in ptx.input_utxos().iter().zip(ptx.tx().inputs()) { + match (inp, utxo) { + (TxInput::Utxo(outpoint), Some(utxo)) => { + let id = match outpoint.source_id() { + OutPointSourceId::Transaction(id) => id.to_hash().0, + OutPointSourceId::BlockReward(id) => id.to_hash().0, + }; + let out = to_trezor_output_msg(chain_config, &utxo.utxo, &utxo.additional_info)?; + utxos.entry(id).or_default().insert(outpoint.output_index(), out); + } + (TxInput::Utxo(_), None) => unimplemented!("missing utxo"), + (TxInput::Account(_) | TxInput::AccountCommand(_, _), Some(_)) => { + panic!("can't have accounts as UTXOs") + } + (TxInput::Account(_) | TxInput::AccountCommand(_, _), _) => {} + } + } + + Ok(utxos) +} + +fn to_trezor_output_msg( + chain_config: &ChainConfig, + out: &TxOutput, + additional_info: &Option, +) -> SignerResult { + let res = match out { TxOutput::Transfer(value, dest) => { let mut out_req = MintlayerTransferTxOutput::new(); - set_value(value, &mut out_req); + out_req.value = Some(to_trezor_output_value(value, additional_info)?).into(); out_req.set_address( Address::new(chain_config, dest.clone()).expect("addressable").into_string(), ); @@ -766,22 +804,7 @@ fn to_trezor_output_msg(chain_config: &ChainConfig, out: &TxOutput) -> Mintlayer } TxOutput::LockThenTransfer(value, dest, lock) => { let mut out_req = MintlayerLockThenTransferTxOutput::new(); - match value { - OutputValue::Coin(amount) => { - let mut value = MintlayerOutputValue::new(); - value.set_amount(amount.into_atoms().to_be_bytes().to_vec()); - out_req.value = Some(value).into(); - } - OutputValue::TokenV1(token_id, amount) => { - let mut value = MintlayerOutputValue::new(); - value.set_amount(amount.into_atoms().to_be_bytes().to_vec()); - value.set_token_id(token_id.to_hash().as_bytes().to_vec()); - out_req.value = Some(value).into(); - } - OutputValue::TokenV0(_) => { - panic!("token v0 unsuported"); - } - }; + out_req.value = Some(to_trezor_output_value(value, additional_info)?).into(); out_req.set_address( Address::new(chain_config, dest.clone()).expect("addressable").into_string(), ); @@ -809,22 +832,7 @@ fn to_trezor_output_msg(chain_config: &ChainConfig, out: &TxOutput) -> Mintlayer } TxOutput::Burn(value) => { let mut out_req = MintlayerBurnTxOutput::new(); - match value { - OutputValue::Coin(amount) => { - let mut value = MintlayerOutputValue::new(); - value.set_amount(amount.into_atoms().to_be_bytes().to_vec()); - out_req.value = Some(value).into(); - } - OutputValue::TokenV1(token_id, amount) => { - let mut value = MintlayerOutputValue::new(); - value.set_amount(amount.into_atoms().to_be_bytes().to_vec()); - value.set_token_id(token_id.to_hash().as_bytes().to_vec()); - out_req.value = Some(value).into(); - } - OutputValue::TokenV0(_) => { - panic!("token v0 unsuported"); - } - }; + out_req.value = Some(to_trezor_output_value(value, additional_info)?).into(); let mut out = MintlayerTxOutput::new(); out.burn = Some(out_req).into(); @@ -975,7 +983,8 @@ fn to_trezor_output_msg(chain_config: &ChainConfig, out: &TxOutput) -> Mintlayer } TxOutput::Htlc(_, _) => unimplemented!("HTLC"), TxOutput::AnyoneCanTake(_) => unimplemented!("AnyoneCanTake"), - } + }; + Ok(res) } #[derive(Clone)] @@ -1028,12 +1037,12 @@ impl TrezorSignerProvider { .mintlayer_get_public_key(account_path) .map_err(|e| SignerError::TrezorError(TrezorError::DeviceError(e)))?; let chain_code = ChainCode::from(xpub.chain_code.0); - let account_pubkey = Secp256k1ExtendedPublicKey::from_hardware_wallet( + let account_pubkey = Secp256k1ExtendedPublicKey::new( derivation_path, chain_code, Secp256k1PublicKey::from_bytes(&xpub.public_key.serialize()).expect(""), ); - let account_pubkey = ExtendedPublicKey::from_hardware_public_key(account_pubkey); + let account_pubkey = ExtendedPublicKey::new(account_pubkey); Ok(account_pubkey) } } diff --git a/wallet/src/signer/trezor_signer/tests.rs b/wallet/src/signer/trezor_signer/tests.rs index 2862594e79..0008c06a20 100644 --- a/wallet/src/signer/trezor_signer/tests.rs +++ b/wallet/src/signer/trezor_signer/tests.rs @@ -210,7 +210,7 @@ fn sign_transaction(#[case] seed: Seed) { .unwrap() .with_inputs_and_destinations(acc_inputs.into_iter().zip(acc_dests.clone())) .with_outputs(outputs); - let ptx = req.into_partially_signed_tx().unwrap(); + let ptx = req.into_partially_signed_tx(&BTreeMap::default()).unwrap(); let mut devices = find_devices(false); assert!(!devices.is_empty()); diff --git a/wallet/src/wallet/mod.rs b/wallet/src/wallet/mod.rs index 5a20caa7f9..1646bb78b8 100644 --- a/wallet/src/wallet/mod.rs +++ b/wallet/src/wallet/mod.rs @@ -19,8 +19,8 @@ use std::sync::Arc; use crate::account::transaction_list::TransactionList; use crate::account::{ - currency_grouper::Currency, CurrentFeeRate, DelegationData, PoolData, TransactionToSign, - UnconfirmedTokenInfo, UtxoSelectorError, + currency_grouper::Currency, CurrentFeeRate, DelegationData, PoolData, UnconfirmedTokenInfo, + UtxoSelectorError, }; use crate::account::{CoinSelectionAlgo, TxInfo}; use crate::key_chain::{ @@ -28,7 +28,8 @@ use crate::key_chain::{ MasterKeyChain, LOOKAHEAD_SIZE, VRF_INDEX, }; use crate::send_request::{ - make_issue_token_outputs, IssueNftArguments, SelectedInputs, StakePoolDataArguments, + make_issue_token_outputs, IssueNftArguments, PoolOrTokenId, SelectedInputs, + StakePoolDataArguments, }; use crate::signer::{Signer, SignerError, SignerProvider}; use crate::wallet_events::{WalletEvents, WalletEventsNoOp}; @@ -40,7 +41,6 @@ use common::chain::block::timestamp::BlockTimestamp; use common::chain::classic_multisig::ClassicMultisigChallenge; use common::chain::htlc::HashedTimelockContract; use common::chain::output_value::OutputValue; -use common::chain::partially_signed_transaction::PartiallySignedTransaction; use common::chain::signature::inputsig::arbitrary_message::{ ArbitraryMessageSignature, SignArbitraryMessageError, }; @@ -49,8 +49,9 @@ use common::chain::tokens::{ make_token_id, IsTokenUnfreezable, Metadata, RPCFungibleTokenInfo, TokenId, TokenIssuance, }; use common::chain::{ - AccountNonce, Block, ChainConfig, DelegationId, Destination, GenBlock, PoolId, - SignedTransaction, Transaction, TransactionCreationError, TxInput, TxOutput, UtxoOutPoint, + AccountCommand, AccountNonce, AccountOutPoint, Block, ChainConfig, DelegationId, Destination, + GenBlock, PoolId, SignedTransaction, Transaction, TransactionCreationError, TxInput, TxOutput, + UtxoOutPoint, }; use common::primitives::id::{hash_encoded, WithId}; use common::primitives::{Amount, BlockHeight, Id, H256}; @@ -74,6 +75,10 @@ use wallet_storage::{ }; use wallet_types::account_info::{StandaloneAddressDetails, StandaloneAddresses}; use wallet_types::chain_info::ChainInfo; +use wallet_types::partially_signed_transaction::{ + PartiallySignedTransaction, PartiallySignedTransactionCreationError, TokenAdditionalInfo, + UtxoAdditionalInfo, +}; use wallet_types::seed_phrase::SerializableSeedPhrase; use wallet_types::signature_status::SignatureStatus; use wallet_types::utxo_types::{UtxoStates, UtxoTypes}; @@ -149,6 +154,8 @@ pub enum WalletError { UnknownTokenId(TokenId), #[error("Transaction creation error: {0}")] TransactionCreation(#[from] TransactionCreationError), + #[error("Transaction creation error: {0}")] + PartiallySignedTransactionCreation(#[from] PartiallySignedTransactionCreationError), #[error("Transaction signing error: {0}")] TransactionSig(#[from] DestinationSigError), #[error("Delegation not found with id {0}")] @@ -237,6 +244,14 @@ pub enum WalletError { CannotChangeTrezorWalletType, #[error("The file being loaded does not correspond to the connected hardware wallet")] HardwareWalletDifferentFile, + #[error("Missing additional data for Pool {0}")] + MissingPoolAdditionalData(PoolId), + #[error("Missing additional data for Token {0}")] + MissingTokenAdditionalData(TokenId), + #[error("Missmatched additional data for token {0}")] + MissmatchedTokenAdditionalData(TokenId), + #[error("Unsupported operation for a Hardware wallet")] + UnsupportedHardwareWalletOperation, } /// Result type used for the wallet @@ -1012,6 +1027,7 @@ where fn for_account_rw_unlocked_and_check_tx_custom_error( &mut self, account_index: U31, + additional_utxo_infos: &BTreeMap, f: impl FnOnce(&mut Account, &mut StoreTxRwUnlocked) -> WalletResult, error_mapper: impl FnOnce(WalletError) -> WalletError, ) -> WalletResult { @@ -1022,14 +1038,14 @@ where |account, db_tx, chain_config, signer_provider| { let request = f(account, db_tx)?; - let ptx = request.into_partially_signed_tx()?; + let ptx = request.into_partially_signed_tx(additional_utxo_infos)?; let mut signer = signer_provider.provide(Arc::new(chain_config.clone()), account_index); let ptx = signer.sign_tx(ptx, account.key_chain(), db_tx).map(|(ptx, _, _)| ptx)?; let inputs_utxo_refs: Vec<_> = - ptx.input_utxos().iter().map(|u| u.as_ref()).collect(); + ptx.input_utxos().iter().map(|u| u.as_ref().map(|x| &x.utxo)).collect(); let is_fully_signed = ptx.destinations().iter().enumerate().zip(ptx.witnesses()).all( |((i, destination), witness)| match (witness, destination) { @@ -1053,9 +1069,9 @@ where ))); } - let tx = ptx - .into_signed_tx() - .map_err(|e| error_mapper(WalletError::TransactionCreation(e)))?; + let tx = ptx.into_signed_tx().map_err(|e| { + error_mapper(WalletError::PartiallySignedTransactionCreation(e)) + })?; check_transaction(chain_config, block_height.next_height(), &tx)?; Ok(tx) @@ -1066,9 +1082,15 @@ where fn for_account_rw_unlocked_and_check_tx( &mut self, account_index: U31, + additional_utxo_infos: &BTreeMap, f: impl FnOnce(&mut Account, &mut StoreTxRwUnlocked) -> WalletResult, ) -> WalletResult { - self.for_account_rw_unlocked_and_check_tx_custom_error(account_index, f, |err| err) + self.for_account_rw_unlocked_and_check_tx_custom_error( + account_index, + additional_utxo_infos, + f, + |err| err, + ) } fn get_account(&self, account_index: U31) -> WalletResult<&Account> { @@ -1105,7 +1127,7 @@ where utxo_types: UtxoTypes, utxo_states: UtxoStates, with_locked: WithLocked, - ) -> WalletResult)>> { + ) -> WalletResult> { let account = self.get_account(account_index)?; let utxos = account.get_multisig_utxos( utxo_types, @@ -1113,10 +1135,7 @@ where utxo_states, with_locked, ); - let utxos = utxos - .into_iter() - .map(|(outpoint, (txo, token_id))| (outpoint, txo.clone(), token_id)) - .collect(); + let utxos = utxos.into_iter().map(|(outpoint, txo)| (outpoint, txo.clone())).collect(); Ok(utxos) } @@ -1126,7 +1145,7 @@ where utxo_types: UtxoTypes, utxo_states: UtxoStates, with_locked: WithLocked, - ) -> WalletResult)>> { + ) -> WalletResult> { let account = self.get_account(account_index)?; let utxos = account.get_utxos( utxo_types, @@ -1134,13 +1153,22 @@ where utxo_states, with_locked, ); - let utxos = utxos - .into_iter() - .map(|(outpoint, (txo, token_id))| (outpoint, txo.clone(), token_id)) - .collect(); + let utxos = utxos.into_iter().map(|(outpoint, txo)| (outpoint, txo.clone())).collect(); Ok(utxos) } + pub fn find_account_destination(&self, acc_outpoint: &AccountOutPoint) -> Option { + self.accounts + .values() + .find_map(|acc| acc.find_account_destination(acc_outpoint).ok()) + } + + pub fn find_account_command_destination(&self, cmd: &AccountCommand) -> Option { + self.accounts + .values() + .find_map(|acc| acc.find_account_command_destination(cmd).ok()) + } + pub fn find_unspent_utxo_with_destination( &self, outpoint: &UtxoOutPoint, @@ -1361,10 +1389,13 @@ where /// current_fee_rate is lower than the consolidate_fee_rate then the wallet will tend to /// use and consolidate multiple smaller inputs, else if the current_fee_rate is higher it will /// tend to use inputs with lowest fee. + /// * `additional_utxo_infos` - Any additional info for Tokens or Pools used in the UTXOs of + /// the transaction to be created /// /// # Returns /// /// A `WalletResult` containing the signed transaction if successful, or an error indicating the reason for failure. + #[allow(clippy::too_many_arguments)] pub fn create_transaction_to_addresses( &mut self, account_index: U31, @@ -1373,22 +1404,27 @@ where change_addresses: BTreeMap>, current_fee_rate: FeeRate, consolidate_fee_rate: FeeRate, + additional_utxo_infos: &BTreeMap, ) -> WalletResult { let request = SendRequest::new().with_outputs(outputs); let latest_median_time = self.latest_median_time; - self.for_account_rw_unlocked_and_check_tx(account_index, |account, db_tx| { - account.process_send_request_and_sign( - db_tx, - request, - inputs, - change_addresses, - latest_median_time, - CurrentFeeRate { - current_fee_rate, - consolidate_fee_rate, - }, - ) - }) + self.for_account_rw_unlocked_and_check_tx( + account_index, + additional_utxo_infos, + |account, db_tx| { + account.process_send_request_and_sign( + db_tx, + request, + inputs, + change_addresses, + latest_median_time, + CurrentFeeRate { + current_fee_rate, + consolidate_fee_rate, + }, + ) + }, + ) } #[allow(clippy::too_many_arguments)] @@ -1401,6 +1437,7 @@ where change_addresses: BTreeMap>, current_fee_rate: FeeRate, consolidate_fee_rate: FeeRate, + additional_utxo_infos: &BTreeMap, ) -> WalletResult<(PartiallySignedTransaction, BTreeMap)> { let request = SendRequest::new().with_outputs(outputs); let latest_median_time = self.latest_median_time; @@ -1416,6 +1453,7 @@ where current_fee_rate, consolidate_fee_rate, }, + additional_utxo_infos, ) }) } @@ -1424,19 +1462,20 @@ where &mut self, account_index: U31, destination: Destination, - inputs: Vec<(UtxoOutPoint, TxOutput, Option)>, + inputs: Vec<(UtxoOutPoint, TxOutput)>, current_fee_rate: FeeRate, + additional_utxo_infos: &BTreeMap, ) -> WalletResult { let request = SendRequest::new().with_inputs( - inputs - .into_iter() - .map(|(outpoint, output, _)| (TxInput::Utxo(outpoint), output)), + inputs.into_iter().map(|(outpoint, output)| (TxInput::Utxo(outpoint), output)), &|_| None, )?; - self.for_account_rw_unlocked_and_check_tx(account_index, |account, _| { - account.sweep_addresses(destination, request, current_fee_rate) - }) + self.for_account_rw_unlocked_and_check_tx( + account_index, + additional_utxo_infos, + |account, _| account.sweep_addresses(destination, request, current_fee_rate), + ) } pub fn create_sweep_from_delegation_transaction( @@ -1447,7 +1486,7 @@ where delegation_share: Amount, current_fee_rate: FeeRate, ) -> WalletResult { - self.for_account_rw_unlocked_and_check_tx(account_index, |account, _| { + self.for_account_rw_unlocked_and_check_tx(account_index, &BTreeMap::new(), |account, _| { account.sweep_delegation(address, delegation_id, delegation_share, current_fee_rate) }) } @@ -1461,7 +1500,7 @@ where delegation_share: Amount, current_fee_rate: FeeRate, ) -> WalletResult { - self.for_account_rw_unlocked_and_check_tx(account_index, |account, _| { + self.for_account_rw_unlocked_and_check_tx(account_index, &BTreeMap::new(), |account, _| { account.spend_from_delegation( address, amount, @@ -1482,19 +1521,24 @@ where consolidate_fee_rate: FeeRate, ) -> WalletResult { let latest_median_time = self.latest_median_time; - self.for_account_rw_unlocked_and_check_tx(account_index, |account, db_tx| { - account.mint_tokens( - db_tx, - token_info, - destination, - amount, - latest_median_time, - CurrentFeeRate { - current_fee_rate, - consolidate_fee_rate, - }, - ) - }) + let additional_utxo_infos = to_token_additional_info(token_info); + self.for_account_rw_unlocked_and_check_tx( + account_index, + &additional_utxo_infos, + |account, db_tx| { + account.mint_tokens( + db_tx, + token_info, + destination, + amount, + latest_median_time, + CurrentFeeRate { + current_fee_rate, + consolidate_fee_rate, + }, + ) + }, + ) } pub fn unmint_tokens( @@ -1506,18 +1550,23 @@ where consolidate_fee_rate: FeeRate, ) -> WalletResult { let latest_median_time = self.latest_median_time; - self.for_account_rw_unlocked_and_check_tx(account_index, |account, db_tx| { - account.unmint_tokens( - db_tx, - token_info, - amount, - latest_median_time, - CurrentFeeRate { - current_fee_rate, - consolidate_fee_rate, - }, - ) - }) + let additional_utxo_infos = to_token_additional_info(token_info); + self.for_account_rw_unlocked_and_check_tx( + account_index, + &additional_utxo_infos, + |account, db_tx| { + account.unmint_tokens( + db_tx, + token_info, + amount, + latest_median_time, + CurrentFeeRate { + current_fee_rate, + consolidate_fee_rate, + }, + ) + }, + ) } pub fn lock_token_supply( @@ -1528,17 +1577,22 @@ where consolidate_fee_rate: FeeRate, ) -> WalletResult { let latest_median_time = self.latest_median_time; - self.for_account_rw_unlocked_and_check_tx(account_index, |account, db_tx| { - account.lock_token_supply( - db_tx, - token_info, - latest_median_time, - CurrentFeeRate { - current_fee_rate, - consolidate_fee_rate, - }, - ) - }) + let additional_utxo_infos = to_token_additional_info(token_info); + self.for_account_rw_unlocked_and_check_tx( + account_index, + &additional_utxo_infos, + |account, db_tx| { + account.lock_token_supply( + db_tx, + token_info, + latest_median_time, + CurrentFeeRate { + current_fee_rate, + consolidate_fee_rate, + }, + ) + }, + ) } pub fn freeze_token( @@ -1550,18 +1604,23 @@ where consolidate_fee_rate: FeeRate, ) -> WalletResult { let latest_median_time = self.latest_median_time; - self.for_account_rw_unlocked_and_check_tx(account_index, |account, db_tx| { - account.freeze_token( - db_tx, - token_info, - is_token_unfreezable, - latest_median_time, - CurrentFeeRate { - current_fee_rate, - consolidate_fee_rate, - }, - ) - }) + let additional_utxo_infos = to_token_additional_info(token_info); + self.for_account_rw_unlocked_and_check_tx( + account_index, + &additional_utxo_infos, + |account, db_tx| { + account.freeze_token( + db_tx, + token_info, + is_token_unfreezable, + latest_median_time, + CurrentFeeRate { + current_fee_rate, + consolidate_fee_rate, + }, + ) + }, + ) } pub fn unfreeze_token( @@ -1572,17 +1631,22 @@ where consolidate_fee_rate: FeeRate, ) -> WalletResult { let latest_median_time = self.latest_median_time; - self.for_account_rw_unlocked_and_check_tx(account_index, |account, db_tx| { - account.unfreeze_token( - db_tx, - token_info, - latest_median_time, - CurrentFeeRate { - current_fee_rate, - consolidate_fee_rate, - }, - ) - }) + let additional_utxo_infos = to_token_additional_info(token_info); + self.for_account_rw_unlocked_and_check_tx( + account_index, + &additional_utxo_infos, + |account, db_tx| { + account.unfreeze_token( + db_tx, + token_info, + latest_median_time, + CurrentFeeRate { + current_fee_rate, + consolidate_fee_rate, + }, + ) + }, + ) } pub fn change_token_authority( @@ -1594,18 +1658,23 @@ where consolidate_fee_rate: FeeRate, ) -> WalletResult { let latest_median_time = self.latest_median_time; - self.for_account_rw_unlocked_and_check_tx(account_index, |account, db_tx| { - account.change_token_authority( - db_tx, - token_info, - address, - latest_median_time, - CurrentFeeRate { - current_fee_rate, - consolidate_fee_rate, - }, - ) - }) + let additional_utxo_infos = to_token_additional_info(token_info); + self.for_account_rw_unlocked_and_check_tx( + account_index, + &additional_utxo_infos, + |account, db_tx| { + account.change_token_authority( + db_tx, + token_info, + address, + latest_median_time, + CurrentFeeRate { + current_fee_rate, + consolidate_fee_rate, + }, + ) + }, + ) } pub fn find_used_tokens( @@ -1620,7 +1689,7 @@ where pub fn get_token_unconfirmed_info( &self, account_index: U31, - token_info: &RPCFungibleTokenInfo, + token_info: RPCFungibleTokenInfo, ) -> WalletResult { self.get_account(account_index)?.get_token_unconfirmed_info(token_info) } @@ -1639,6 +1708,7 @@ where BTreeMap::new(), current_fee_rate, consolidate_fee_rate, + &BTreeMap::new(), )?; let input0_outpoint = tx .transaction() @@ -1667,6 +1737,7 @@ where BTreeMap::new(), current_fee_rate, consolidate_fee_rate, + &BTreeMap::new(), )?; let token_id = make_token_id(tx.transaction().inputs()).ok_or(WalletError::MissingTokenId)?; @@ -1684,8 +1755,10 @@ where let destination = address.into_object(); let latest_median_time = self.latest_median_time; - let signed_transaction = - self.for_account_rw_unlocked_and_check_tx(account_index, |account, db_tx| { + let signed_transaction = self.for_account_rw_unlocked_and_check_tx( + account_index, + &BTreeMap::new(), + |account, db_tx| { account.create_issue_nft_tx( db_tx, IssueNftArguments { @@ -1698,7 +1771,8 @@ where consolidate_fee_rate, }, ) - })?; + }, + )?; let token_id = make_token_id(signed_transaction.transaction().inputs()) .ok_or(WalletError::MissingTokenId)?; @@ -1709,17 +1783,22 @@ where &mut self, account_index: U31, pool_id: PoolId, - pool_balance: Amount, + staker_balance: Amount, output_address: Option, current_fee_rate: FeeRate, ) -> WalletResult { + let additional_utxo_infos = BTreeMap::from_iter([( + PoolOrTokenId::PoolId(pool_id), + UtxoAdditionalInfo::PoolInfo { staker_balance }, + )]); self.for_account_rw_unlocked_and_check_tx_custom_error( account_index, - |account, db_tx| { + &additional_utxo_infos, + |account: &mut Account<

::K>, db_tx| { account.decommission_stake_pool( db_tx, pool_id, - pool_balance, + staker_balance, output_address, current_fee_rate, ) @@ -1732,22 +1811,26 @@ where &mut self, account_index: U31, pool_id: PoolId, - pool_balance: Amount, + staker_balance: Amount, output_address: Option, current_fee_rate: FeeRate, ) -> WalletResult { + let additional_utxo_infos = BTreeMap::from_iter([( + PoolOrTokenId::PoolId(pool_id), + UtxoAdditionalInfo::PoolInfo { staker_balance }, + )]); self.for_account_rw_unlocked( account_index, |account, db_tx, chain_config, signer_provider| { let request = account.decommission_stake_pool_request( db_tx, pool_id, - pool_balance, + staker_balance, output_address, current_fee_rate, )?; - let ptx = request.into_partially_signed_tx()?; + let ptx = request.into_partially_signed_tx(&additional_utxo_infos)?; let mut signer = signer_provider.provide(Arc::new(chain_config.clone()), account_index); @@ -1768,41 +1851,39 @@ where htlc: HashedTimelockContract, current_fee_rate: FeeRate, consolidate_fee_rate: FeeRate, + additional_utxo_infos: &BTreeMap, ) -> WalletResult { let latest_median_time = self.latest_median_time; - self.for_account_rw_unlocked_and_check_tx(account_index, |account, db_tx| { - account.create_htlc_tx( - db_tx, - output_value, - htlc, - latest_median_time, - CurrentFeeRate { - current_fee_rate, - consolidate_fee_rate, - }, - ) - }) + self.for_account_rw_unlocked_and_check_tx( + account_index, + additional_utxo_infos, + |account, db_tx| { + account.create_htlc_tx( + db_tx, + output_value, + htlc, + latest_median_time, + CurrentFeeRate { + current_fee_rate, + consolidate_fee_rate, + }, + ) + }, + ) } pub fn sign_raw_transaction( &mut self, account_index: U31, - tx: TransactionToSign, + ptx: PartiallySignedTransaction, ) -> WalletResult<( PartiallySignedTransaction, Vec, Vec, )> { - let latest_median_time = self.latest_median_time; self.for_account_rw_unlocked( account_index, |account, db_tx, chain_config, signer_provider| { - let ptx = match tx { - TransactionToSign::Partial(ptx) => ptx, - TransactionToSign::Tx(tx) => { - account.tx_to_partially_signed_tx(tx, latest_median_time)? - } - }; let mut signer = signer_provider.provide(Arc::new(chain_config.clone()), account_index); @@ -1997,6 +2078,18 @@ where } } +fn to_token_additional_info( + token_info: &UnconfirmedTokenInfo, +) -> BTreeMap { + BTreeMap::from_iter([( + PoolOrTokenId::TokenId(token_info.token_id()), + UtxoAdditionalInfo::TokenInfo(TokenAdditionalInfo { + num_decimals: token_info.num_decimals(), + ticker: token_info.token_ticker().to_vec(), + }), + )]) +} + impl Wallet where B: storage::Backend + 'static, @@ -2035,17 +2128,21 @@ where stake_pool_arguments: StakePoolDataArguments, ) -> WalletResult { let latest_median_time = self.latest_median_time; - self.for_account_rw_unlocked_and_check_tx(account_index, |account, db_tx| { - account.create_stake_pool_tx( - db_tx, - stake_pool_arguments, - latest_median_time, - CurrentFeeRate { - current_fee_rate, - consolidate_fee_rate, - }, - ) - }) + self.for_account_rw_unlocked_and_check_tx( + account_index, + &BTreeMap::new(), + |account, db_tx| { + account.create_stake_pool_tx( + db_tx, + stake_pool_arguments, + latest_median_time, + CurrentFeeRate { + current_fee_rate, + consolidate_fee_rate, + }, + ) + }, + ) } pub fn get_pos_gen_block_data( &self, diff --git a/wallet/src/wallet/tests.rs b/wallet/src/wallet/tests.rs index 6958280a8d..d7be41b0fe 100644 --- a/wallet/src/wallet/tests.rs +++ b/wallet/src/wallet/tests.rs @@ -54,6 +54,10 @@ use test_utils::random::{make_seedable_rng, Seed}; use wallet_storage::{schema, WalletStorageEncryptionRead}; use wallet_types::{ account_info::DEFAULT_ACCOUNT_INDEX, + partially_signed_transaction::{ + PartiallySignedTransaction, PartiallySignedTransactionCreationError, UtxoAdditionalInfo, + UtxoWithAdditionalInfo, + }, seed_phrase::{PassPhrase, StoreSeedPhrase}, utxo_types::{UtxoState, UtxoType}, }; @@ -994,6 +998,7 @@ fn wallet_accounts_creation() { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); @@ -1153,6 +1158,7 @@ fn locked_wallet_cant_sign_transaction(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ), Err(WalletError::DatabaseError( wallet_storage::Error::WalletLocked @@ -1170,6 +1176,7 @@ fn locked_wallet_cant_sign_transaction(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); } else { @@ -1199,6 +1206,7 @@ fn locked_wallet_cant_sign_transaction(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); } @@ -1226,6 +1234,7 @@ fn wallet_get_transaction(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); @@ -1285,6 +1294,7 @@ fn wallet_list_mainchain_transactions(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); @@ -1307,6 +1317,7 @@ fn wallet_list_mainchain_transactions(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); let spend_from_tx_id = tx.transaction().get_id(); @@ -1361,6 +1372,7 @@ fn wallet_transaction_with_fees(#[case] seed: Seed) { BTreeMap::new(), very_big_feerate, very_big_feerate, + &BTreeMap::new(), ) .unwrap_err(); @@ -1388,6 +1400,7 @@ fn wallet_transaction_with_fees(#[case] seed: Seed) { BTreeMap::new(), feerate, feerate, + &BTreeMap::new(), ) .unwrap(); @@ -1474,6 +1487,7 @@ fn spend_from_user_specified_utxos(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap_err(); assert_eq!(err, WalletError::CannotFindUtxo(missing_utxo.clone())); @@ -1487,7 +1501,7 @@ fn spend_from_user_specified_utxos(#[case] seed: Seed) { let selected_utxos = utxos .iter() - .map(|(outpoint, _, _)| outpoint) + .map(|(outpoint, _)| outpoint) .take(rng.gen_range(1..utxos.len())) .cloned() .collect_vec(); @@ -1500,6 +1514,7 @@ fn spend_from_user_specified_utxos(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); @@ -1537,6 +1552,7 @@ fn spend_from_user_specified_utxos(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap_err(); @@ -1619,7 +1635,7 @@ fn create_stake_pool_and_list_pool_ids(#[case] seed: Seed) { ) .unwrap(); assert_eq!(create_stake_pool_utxos.len(), 1); - let (_, output, _) = create_stake_pool_utxos.pop().unwrap(); + let (_, output) = create_stake_pool_utxos.pop().unwrap(); match output { TxOutput::CreateStakePool(id, data) => { assert_eq!(id, *pool_id); @@ -1767,6 +1783,7 @@ fn send_to_unknown_delegation(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); @@ -1901,6 +1918,7 @@ fn create_spend_from_delegations(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); @@ -2088,10 +2106,12 @@ fn issue_and_transfer_tokens(#[case] seed: Seed) { let amount_fraction = (block1_amount.into_atoms() - NETWORK_FEE) / 10; let mut token_amount_to_issue = Amount::from_atoms(rng.gen_range(1..amount_fraction)); + let mut number_of_decimals = rng.gen_range(1..18); + let token_ticker = "XXXX".as_bytes().to_vec(); let (issued_token_id, token_issuance_transaction) = if issue_fungible_token { let token_issuance = TokenIssuanceV1 { - token_ticker: "XXXX".as_bytes().to_vec(), - number_of_decimals: rng.gen_range(1..18), + token_ticker: token_ticker.clone(), + number_of_decimals, metadata_uri: "http://uri".as_bytes().to_vec(), total_supply: common::chain::tokens::TokenTotalSupply::Unlimited, authority: address2.as_object().clone(), @@ -2128,7 +2148,7 @@ fn issue_and_transfer_tokens(#[case] seed: Seed) { .unwrap(); let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info).unwrap(); let mint_transaction = wallet .mint_tokens( DEFAULT_ACCOUNT_INDEX, @@ -2145,6 +2165,7 @@ fn issue_and_transfer_tokens(#[case] seed: Seed) { vec![token_issuance_transaction, mint_transaction], ) } else { + number_of_decimals = 0; token_amount_to_issue = Amount::from_atoms(1); let (issued_token_id, token_issuance_transaction) = wallet .issue_new_nft( @@ -2154,7 +2175,7 @@ fn issue_and_transfer_tokens(#[case] seed: Seed) { creator: None, name: "Name".as_bytes().to_vec(), description: "SomeNFT".as_bytes().to_vec(), - ticker: "XXXX".as_bytes().to_vec(), + ticker: token_ticker.clone(), icon_uri: DataOrNoVec::from(None), additional_metadata_uri: DataOrNoVec::from(None), media_uri: DataOrNoVec::from(None), @@ -2199,6 +2220,13 @@ fn issue_and_transfer_tokens(#[case] seed: Seed) { Destination::PublicKeyHash(some_other_address), ); + let additional_info = BTreeMap::from_iter([( + PoolOrTokenId::TokenId(*token_id), + UtxoAdditionalInfo::TokenInfo(TokenAdditionalInfo { + num_decimals: number_of_decimals, + ticker: token_ticker.clone(), + }), + )]); let transfer_tokens_transaction = wallet .create_transaction_to_addresses( DEFAULT_ACCOUNT_INDEX, @@ -2207,6 +2235,7 @@ fn issue_and_transfer_tokens(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &additional_info, ) .unwrap(); wallet @@ -2260,6 +2289,7 @@ fn issue_and_transfer_tokens(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &additional_info, ) .err() .unwrap(); @@ -2304,13 +2334,15 @@ fn check_tokens_v0_are_ignored(#[case] seed: Seed) { assert_eq!(coin_balance, block1_amount); let address2 = wallet.get_new_address(DEFAULT_ACCOUNT_INDEX).unwrap().1; + let token_ticker = "XXXX".as_bytes().to_vec(); + let number_of_decimals = rng.gen_range(1..18); let result = wallet.create_transaction_to_addresses( DEFAULT_ACCOUNT_INDEX, [TxOutput::Transfer( OutputValue::TokenV0(Box::new(TokenData::TokenIssuance(Box::new( TokenIssuanceV0 { - token_ticker: "XXXX".as_bytes().to_vec(), - number_of_decimals: rng.gen_range(1..18), + token_ticker, + number_of_decimals, metadata_uri: "http://uri".as_bytes().to_vec(), amount_to_issue: Amount::from_atoms(rng.gen_range(1..10000)), }, @@ -2321,6 +2353,7 @@ fn check_tokens_v0_are_ignored(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ); matches!( @@ -2400,8 +2433,9 @@ fn freeze_and_unfreeze_tokens(#[case] seed: Seed) { token_issuance.authority, ); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); let amount_to_mint = Amount::from_atoms(rng.gen_range(1..=fixed_max_amount.into_atoms())); let mint_tx = wallet @@ -2417,8 +2451,9 @@ fn freeze_and_unfreeze_tokens(#[case] seed: Seed) { let _ = create_block(&chain_config, &mut wallet, vec![mint_tx], block2_amount, 2); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); let freeze_tx = wallet .freeze_token( @@ -2432,8 +2467,9 @@ fn freeze_and_unfreeze_tokens(#[case] seed: Seed) { wallet.add_unconfirmed_tx(freeze_tx.clone(), &WalletEventsNoOp).unwrap(); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); assert_eq!( unconfirmed_token_info.check_can_freeze().unwrap_err(), @@ -2459,8 +2495,9 @@ fn freeze_and_unfreeze_tokens(#[case] seed: Seed) { wallet.add_unconfirmed_tx(unfreeze_tx.clone(), &WalletEventsNoOp).unwrap(); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); unconfirmed_token_info.check_can_freeze().unwrap(); assert_eq!( @@ -2478,8 +2515,9 @@ fn freeze_and_unfreeze_tokens(#[case] seed: Seed) { .abandon_transaction(DEFAULT_ACCOUNT_INDEX, freeze_tx.transaction().get_id()) .unwrap(); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); unconfirmed_token_info.check_can_freeze().unwrap(); assert_eq!( @@ -2500,8 +2538,9 @@ fn freeze_and_unfreeze_tokens(#[case] seed: Seed) { 3, ); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); assert_eq!( unconfirmed_token_info.get_next_nonce().unwrap(), @@ -2532,6 +2571,13 @@ fn freeze_and_unfreeze_tokens(#[case] seed: Seed) { Destination::PublicKeyHash(some_other_address), ); + let additional_info = BTreeMap::from_iter([( + PoolOrTokenId::TokenId(issued_token_id), + UtxoAdditionalInfo::TokenInfo(TokenAdditionalInfo { + num_decimals: unconfirmed_token_info.num_decimals(), + ticker: unconfirmed_token_info.token_ticker().to_vec(), + }), + )]); let transfer_tokens_transaction = wallet .create_transaction_to_addresses( DEFAULT_ACCOUNT_INDEX, @@ -2540,6 +2586,7 @@ fn freeze_and_unfreeze_tokens(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &additional_info, ) .unwrap(); @@ -2548,8 +2595,9 @@ fn freeze_and_unfreeze_tokens(#[case] seed: Seed) { .unwrap(); wallet.add_unconfirmed_tx(freeze_tx.clone(), &WalletEventsNoOp).unwrap(); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); assert_eq!( unconfirmed_token_info.check_can_freeze().unwrap_err(), @@ -2683,8 +2731,9 @@ fn change_token_supply_fixed(#[case] seed: Seed) { token_issuance.authority, ); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); assert_eq!( unconfirmed_token_info.authority().unwrap(), @@ -2715,8 +2764,9 @@ fn change_token_supply_fixed(#[case] seed: Seed) { wallet.add_unconfirmed_tx(mint_transaction.clone(), &WalletEventsNoOp).unwrap(); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); // Try to mint more then the fixed maximum let leftover = (fixed_max_amount - token_amount_to_mint).unwrap(); @@ -2761,8 +2811,9 @@ fn change_token_supply_fixed(#[case] seed: Seed) { ) .unwrap(); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); assert_eq!( unconfirmed_token_info.get_next_nonce().unwrap(), @@ -2784,8 +2835,9 @@ fn change_token_supply_fixed(#[case] seed: Seed) { ); token_info.circulating_supply = unconfirmed_token_info.current_supply().unwrap(); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); assert_eq!( unconfirmed_token_info.get_next_nonce().unwrap(), @@ -2828,8 +2880,9 @@ fn change_token_supply_fixed(#[case] seed: Seed) { wallet .add_unconfirmed_tx(unmint_transaction.clone(), &WalletEventsNoOp) .unwrap(); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); let _ = create_block( &chain_config, @@ -2840,8 +2893,9 @@ fn change_token_supply_fixed(#[case] seed: Seed) { ); token_info.circulating_supply = unconfirmed_token_info.current_supply().unwrap(); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); assert_eq!( unconfirmed_token_info.get_next_nonce().unwrap(), @@ -2931,8 +2985,9 @@ fn change_token_supply_unlimited(#[case] seed: Seed) { token_issuance.authority, ); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); assert_eq!( unconfirmed_token_info.authority().unwrap(), @@ -2962,8 +3017,9 @@ fn change_token_supply_unlimited(#[case] seed: Seed) { .unwrap(); wallet.add_unconfirmed_tx(mint_transaction.clone(), &WalletEventsNoOp).unwrap(); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); let _ = create_block( &chain_config, @@ -2974,8 +3030,9 @@ fn change_token_supply_unlimited(#[case] seed: Seed) { ); token_info.circulating_supply = unconfirmed_token_info.current_supply().unwrap(); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); assert_eq!( unconfirmed_token_info.get_next_nonce().unwrap(), @@ -3017,8 +3074,9 @@ fn change_token_supply_unlimited(#[case] seed: Seed) { wallet .add_unconfirmed_tx(unmint_transaction.clone(), &WalletEventsNoOp) .unwrap(); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); let _ = create_block( &chain_config, @@ -3029,8 +3087,9 @@ fn change_token_supply_unlimited(#[case] seed: Seed) { ); token_info.circulating_supply = unconfirmed_token_info.current_supply().unwrap(); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); assert_eq!( unconfirmed_token_info.get_next_nonce().unwrap(), @@ -3120,8 +3179,9 @@ fn change_and_lock_token_supply_lockable(#[case] seed: Seed) { token_issuance.authority, ); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); assert_eq!( unconfirmed_token_info.authority().unwrap(), @@ -3150,8 +3210,9 @@ fn change_and_lock_token_supply_lockable(#[case] seed: Seed) { ) .unwrap(); wallet.add_unconfirmed_tx(mint_transaction.clone(), &WalletEventsNoOp).unwrap(); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); let _ = create_block( &chain_config, @@ -3162,8 +3223,9 @@ fn change_and_lock_token_supply_lockable(#[case] seed: Seed) { ); token_info.circulating_supply = unconfirmed_token_info.current_supply().unwrap(); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); assert_eq!( unconfirmed_token_info.get_next_nonce().unwrap(), @@ -3206,8 +3268,9 @@ fn change_and_lock_token_supply_lockable(#[case] seed: Seed) { wallet .add_unconfirmed_tx(unmint_transaction.clone(), &WalletEventsNoOp) .unwrap(); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); let _ = create_block( &chain_config, @@ -3218,8 +3281,9 @@ fn change_and_lock_token_supply_lockable(#[case] seed: Seed) { ); token_info.circulating_supply = unconfirmed_token_info.current_supply().unwrap(); - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); assert_eq!( unconfirmed_token_info.get_next_nonce().unwrap(), @@ -3250,8 +3314,9 @@ fn change_and_lock_token_supply_lockable(#[case] seed: Seed) { ); token_info.is_locked = true; - let unconfirmed_token_info = - wallet.get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, &token_info).unwrap(); + let unconfirmed_token_info = wallet + .get_token_unconfirmed_info(DEFAULT_ACCOUNT_INDEX, token_info.clone()) + .unwrap(); assert_eq!( unconfirmed_token_info.get_next_nonce().unwrap(), @@ -3369,6 +3434,7 @@ fn lock_then_transfer(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); wallet @@ -3490,6 +3556,7 @@ fn wallet_multiple_transactions_in_single_block(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); wallet.add_unconfirmed_tx(transaction.clone(), &WalletEventsNoOp).unwrap(); @@ -3581,6 +3648,7 @@ fn wallet_scan_multiple_transactions_from_mempool(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); @@ -3616,6 +3684,7 @@ fn wallet_scan_multiple_transactions_from_mempool(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); wallet.add_unconfirmed_tx(transaction.clone(), &WalletEventsNoOp).unwrap(); @@ -3655,6 +3724,7 @@ fn wallet_scan_multiple_transactions_from_mempool(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap_err(); assert_eq!( @@ -3681,6 +3751,7 @@ fn wallet_scan_multiple_transactions_from_mempool(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); wallet.add_unconfirmed_tx(transaction.clone(), &WalletEventsNoOp).unwrap(); @@ -3765,6 +3836,7 @@ fn wallet_abandone_transactions(#[case] seed: Seed) { BTreeMap::new(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); wallet @@ -4082,7 +4154,7 @@ fn decommission_pool_request_wrong_account(#[case] seed: Seed) { assert!(!decommission_partial_tx.all_signatures_available()); matches!( decommission_partial_tx.into_signed_tx().unwrap_err(), - TransactionCreationError::FailedToConvertPartiallySignedTx(_) + PartiallySignedTransactionCreationError::FailedToConvertPartiallySignedTx(_) ); } @@ -4103,7 +4175,8 @@ fn sign_decommission_pool_request_between_accounts(#[case] seed: Seed) { // Generate a new block which sends reward to the wallet let block1_amount = Amount::from_atoms(rng.gen_range(NETWORK_FEE + 100..NETWORK_FEE + 10000)); - let _ = create_block(&chain_config, &mut wallet, vec![], block1_amount, 0); + let (addr, _) = create_block(&chain_config, &mut wallet, vec![], block1_amount, 0); + let utxo = make_address_output(addr.clone(), block1_amount); let pool_ids = wallet.get_pool_ids(acc_0_index, WalletPoolsFilter::All).unwrap(); assert!(pool_ids.is_empty()); @@ -4134,8 +4207,19 @@ fn sign_decommission_pool_request_between_accounts(#[case] seed: Seed) { // remove the signatures and try to sign it again let tx = stake_pool_transaction.transaction().clone(); + let inps = tx.inputs().len(); + let outs = tx.outputs().len(); + let ptx = PartiallySignedTransaction::new( + tx, + vec![None; inps], + vec![Some(UtxoWithAdditionalInfo::new(utxo, None))], + vec![Some(addr.into_object())], + None, + vec![None; outs], + ) + .unwrap(); let stake_pool_transaction = wallet - .sign_raw_transaction(acc_0_index, TransactionToSign::Tx(tx)) + .sign_raw_transaction(acc_0_index, ptx) .unwrap() .0 .into_signed_tx() @@ -4167,20 +4251,14 @@ fn sign_decommission_pool_request_between_accounts(#[case] seed: Seed) { // Try to sign decommission request with wrong account let sign_from_acc0_res = wallet - .sign_raw_transaction( - acc_0_index, - TransactionToSign::Partial(decommission_partial_tx.clone()), - ) + .sign_raw_transaction(acc_0_index, decommission_partial_tx.clone()) .unwrap() .0; // the tx is still not fully signed assert!(!sign_from_acc0_res.all_signatures_available()); let signed_tx = wallet - .sign_raw_transaction( - acc_1_index, - TransactionToSign::Partial(decommission_partial_tx), - ) + .sign_raw_transaction(acc_1_index, decommission_partial_tx) .unwrap() .0 .into_signed_tx() @@ -4263,10 +4341,7 @@ fn sign_decommission_pool_request_cold_wallet(#[case] seed: Seed) { // sign the tx with cold wallet let partially_signed_transaction = cold_wallet - .sign_raw_transaction( - DEFAULT_ACCOUNT_INDEX, - TransactionToSign::Partial(decommission_partial_tx), - ) + .sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, decommission_partial_tx) .unwrap() .0; assert!(partially_signed_transaction.all_signatures_available()); @@ -4274,10 +4349,7 @@ fn sign_decommission_pool_request_cold_wallet(#[case] seed: Seed) { // sign it with the hot wallet should leave the signatures in place even if it can't find the // destinations for the inputs let partially_signed_transaction = hot_wallet - .sign_raw_transaction( - DEFAULT_ACCOUNT_INDEX, - TransactionToSign::Partial(partially_signed_transaction), - ) + .sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, partially_signed_transaction) .unwrap() .0; assert!(partially_signed_transaction.all_signatures_available()); @@ -4432,15 +4504,13 @@ fn sign_send_request_cold_wallet(#[case] seed: Seed) { [(Currency::Coin, cold_wallet_address.clone())].into(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); // Try to sign request with the hot wallet let tx = hot_wallet - .sign_raw_transaction( - DEFAULT_ACCOUNT_INDEX, - TransactionToSign::Partial(send_req.clone()), - ) + .sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, send_req.clone()) .unwrap() .0; // the tx is not fully signed @@ -4448,7 +4518,7 @@ fn sign_send_request_cold_wallet(#[case] seed: Seed) { // sign the tx with cold wallet let signed_tx = cold_wallet - .sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, TransactionToSign::Partial(send_req)) + .sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, send_req) .unwrap() .0 .into_signed_tx() @@ -4488,7 +4558,7 @@ fn sign_send_request_cold_wallet(#[case] seed: Seed) { .unwrap(); assert_eq!(utxos.len(), 1); - let (_, output, _) = utxos.pop().unwrap(); + let (_, output) = utxos.pop().unwrap(); matches!(output, TxOutput::Transfer(OutputValue::Coin(value), dest) if value == balance && dest == cold_wallet_address.into_object()); @@ -4537,6 +4607,7 @@ fn test_not_exhaustion_of_keys(#[case] seed: Seed) { [].into(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); } @@ -4670,41 +4741,40 @@ fn test_add_standalone_multisig(#[case] seed: Seed) { )], ) .unwrap(); + let outs = tx.outputs().len(); + let spend_multisig_tx = PartiallySignedTransaction::new( + spend_multisig_tx, + vec![None; 1], + vec![Some(UtxoWithAdditionalInfo::new(tx.outputs()[0].clone(), None))], + vec![Some(multisig_address.as_object().clone())], + None, + vec![None; outs], + ) + .unwrap(); // sign it with wallet1 - let (ptx, _, statuses) = wallet1 - .sign_raw_transaction( - DEFAULT_ACCOUNT_INDEX, - TransactionToSign::Tx(spend_multisig_tx), - ) - .unwrap(); + let (ptx, _, statuses) = + wallet1.sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, spend_multisig_tx).unwrap(); // check it is still not fully signed assert!(ptx.all_signatures_available()); assert!(!statuses.iter().all(|s| *s == SignatureStatus::FullySigned)); // try to sign it with wallet1 again - let (ptx, _, statuses) = wallet1 - .sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, TransactionToSign::Partial(ptx)) - .unwrap(); + let (ptx, _, statuses) = wallet1.sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, ptx).unwrap(); // check it is still not fully signed assert!(ptx.all_signatures_available()); assert!(!statuses.iter().all(|s| *s == SignatureStatus::FullySigned)); // try to sign it with wallet2 but wallet2 does not have the multisig added as standalone - let ptx = wallet2 - .sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, TransactionToSign::Partial(ptx)) - .unwrap() - .0; + let ptx = wallet2.sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, ptx).unwrap().0; // add it to wallet2 as well wallet2.add_standalone_multisig(DEFAULT_ACCOUNT_INDEX, challenge, None).unwrap(); // now we can sign it - let (ptx, _, statuses) = wallet2 - .sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, TransactionToSign::Partial(ptx)) - .unwrap(); + let (ptx, _, statuses) = wallet2.sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, ptx).unwrap(); // now it is fully signed assert!(ptx.all_signatures_available()); @@ -4772,6 +4842,7 @@ fn create_htlc_and_spend(#[case] seed: Seed) { htlc.clone(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); let create_htlc_tx_id = create_htlc_tx.transaction().get_id(); @@ -4807,7 +4878,7 @@ fn create_htlc_and_spend(#[case] seed: Seed) { ) .unwrap(); assert_eq!(wallet2_utxos.len(), 1); - let (_, output, _) = wallet2_utxos.pop().unwrap(); + let (_, output) = wallet2_utxos.pop().unwrap(); match output { TxOutput::Htlc(actual_output_value, actual_htlc) => { assert_eq!(actual_output_value, output_value); @@ -4823,19 +4894,25 @@ fn create_htlc_and_spend(#[case] seed: Seed) { vec![TxOutput::Transfer(output_value, address2.into_object())], ) .unwrap(); - let spend_utxos = vec![create_htlc_tx.transaction().outputs().first().cloned()]; + let spend_utxos = vec![create_htlc_tx + .transaction() + .outputs() + .first() + .cloned() + .map(|out| UtxoWithAdditionalInfo::new(out, None))]; + let outs = create_htlc_tx.outputs().len(); let spend_ptx = PartiallySignedTransaction::new( spend_tx, vec![None], spend_utxos, vec![Some(spend_key.into_object())], Some(vec![Some(secret)]), + vec![None; outs], ) .unwrap(); - let (spend_ptx, _, new_statuses) = wallet2 - .sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, TransactionToSign::Partial(spend_ptx)) - .unwrap(); + let (spend_ptx, _, new_statuses) = + wallet2.sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, spend_ptx).unwrap(); assert_eq!(vec![SignatureStatus::FullySigned], new_statuses); let spend_tx = spend_ptx.into_signed_tx().unwrap(); @@ -4911,6 +4988,7 @@ fn create_htlc_and_refund(#[case] seed: Seed) { htlc.clone(), FeeRate::from_amount_per_kb(Amount::ZERO), FeeRate::from_amount_per_kb(Amount::ZERO), + &BTreeMap::new(), ) .unwrap(); let create_htlc_tx_id = create_htlc_tx.transaction().get_id(); @@ -4921,13 +4999,20 @@ fn create_htlc_and_refund(#[case] seed: Seed) { vec![TxOutput::Transfer(output_value, address1.into_object())], ) .unwrap(); - let refund_utxos = vec![create_htlc_tx.transaction().outputs().first().cloned()]; + let refund_utxos = vec![create_htlc_tx + .transaction() + .outputs() + .first() + .cloned() + .map(|out| UtxoWithAdditionalInfo::new(out, None))]; + let outs = create_htlc_tx.outputs().len(); let refund_ptx = PartiallySignedTransaction::new( refund_tx, vec![None], refund_utxos, vec![Some(refund_key)], None, + vec![None; outs], ) .unwrap(); @@ -4963,12 +5048,8 @@ fn create_htlc_and_refund(#[case] seed: Seed) { .unwrap(); assert_eq!(wallet2_multisig_utxos.len(), 1); - let (refund_ptx, prev_statuses, new_statuses) = wallet2 - .sign_raw_transaction( - DEFAULT_ACCOUNT_INDEX, - TransactionToSign::Partial(refund_ptx), - ) - .unwrap(); + let (refund_ptx, prev_statuses, new_statuses) = + wallet2.sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, refund_ptx).unwrap(); assert_eq!(vec![SignatureStatus::NotSigned], prev_statuses); assert_eq!( @@ -4979,12 +5060,8 @@ fn create_htlc_and_refund(#[case] seed: Seed) { new_statuses ); - let (refund_ptx, prev_statuses, new_statuses) = wallet1 - .sign_raw_transaction( - DEFAULT_ACCOUNT_INDEX, - TransactionToSign::Partial(refund_ptx), - ) - .unwrap(); + let (refund_ptx, prev_statuses, new_statuses) = + wallet1.sign_raw_transaction(DEFAULT_ACCOUNT_INDEX, refund_ptx).unwrap(); assert_eq!( vec![SignatureStatus::PartialMultisig { required_signatures: 2, diff --git a/wallet/types/Cargo.toml b/wallet/types/Cargo.toml index a1141ddf60..90d5761755 100644 --- a/wallet/types/Cargo.toml +++ b/wallet/types/Cargo.toml @@ -10,6 +10,7 @@ rust-version.workspace = true [dependencies] common = { path = "../../common/" } crypto = { path = "../../crypto/" } +tx-verifier = { path = "../../chainstate/tx-verifier" } rpc-description = { path = "../../rpc/description" } randomness = { path = "../../randomness" } serialization = { path = "../../serialization" } diff --git a/wallet/types/src/lib.rs b/wallet/types/src/lib.rs index 8c050b2fd1..8e5cf2b401 100644 --- a/wallet/types/src/lib.rs +++ b/wallet/types/src/lib.rs @@ -17,6 +17,7 @@ pub mod account_id; pub mod account_info; pub mod chain_info; pub mod keys; +pub mod partially_signed_transaction; pub mod seed_phrase; pub mod signature_status; pub mod utxo_types; diff --git a/common/src/chain/transaction/partially_signed_transaction.rs b/wallet/types/src/partially_signed_transaction.rs similarity index 50% rename from common/src/chain/transaction/partially_signed_transaction.rs rename to wallet/types/src/partially_signed_transaction.rs index 3b1e585099..87fea03ee5 100644 --- a/common/src/chain/transaction/partially_signed_transaction.rs +++ b/wallet/types/src/partially_signed_transaction.rs @@ -13,53 +13,128 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{ - htlc::HtlcSecret, - signature::{inputsig::InputWitness, Signable, Transactable}, - Destination, Transaction, TxOutput, +use common::{ + chain::{ + htlc::HtlcSecret, + signature::{inputsig::InputWitness, Signable, Transactable}, + Destination, SignedTransaction, Transaction, TransactionCreationError, TxInput, TxOutput, + }, + primitives::Amount, }; -use crate::chain::{SignedTransaction, TransactionCreationError, TxInput}; use serialization::{Decode, Encode}; +use thiserror::Error; +use tx_verifier::input_check::signature_only_check::SignatureOnlyVerifiable; use utils::ensure; +#[derive(Error, Debug, Clone, PartialEq, Eq)] +pub enum PartiallySignedTransactionCreationError { + #[error("Failed to convert partially signed tx to signed")] + FailedToConvertPartiallySignedTx(PartiallySignedTransaction), + #[error("The number of output additional infos does not match the number of outputs")] + InvalidOutputAdditionalInfoCount, + #[error("Failed to create transaction: {0}")] + TxCreationError(#[from] TransactionCreationError), + #[error("The number of input utxos does not match the number of inputs")] + InvalidInputUtxosCount, + #[error("The number of destinations does not match the number of inputs")] + InvalidDestinationsCount, + #[error("The number of htlc secrets does not match the number of inputs")] + InvalidHtlcSecretsCount, +} + +#[derive(Debug, Eq, PartialEq, Clone, Encode, Decode)] +pub struct TokenAdditionalInfo { + pub num_decimals: u8, + pub ticker: Vec, +} + +/// Additional info for UTXOs +#[derive(Debug, Eq, PartialEq, Clone, Encode, Decode)] +pub enum UtxoAdditionalInfo { + TokenInfo(TokenAdditionalInfo), + PoolInfo { + staker_balance: Amount, + }, + AnyoneCanTake { + ask: Option, + give: Option, + }, +} + +#[derive(Debug, Eq, PartialEq, Clone, Encode, Decode)] +pub struct UtxoWithAdditionalInfo { + pub utxo: TxOutput, + pub additional_info: Option, +} + +impl UtxoWithAdditionalInfo { + pub fn new(utxo: TxOutput, additional_info: Option) -> Self { + Self { + utxo, + additional_info, + } + } +} + #[derive(Debug, Eq, PartialEq, Clone, Encode, Decode)] pub struct PartiallySignedTransaction { tx: Transaction, witnesses: Vec>, - input_utxos: Vec>, + input_utxos: Vec>, destinations: Vec>, htlc_secrets: Vec>, + output_additional_infos: Vec>, } impl PartiallySignedTransaction { pub fn new( tx: Transaction, witnesses: Vec>, - input_utxos: Vec>, + input_utxos: Vec>, destinations: Vec>, htlc_secrets: Option>>, - ) -> Result { + output_additional_infos: Vec>, + ) -> Result { ensure!( tx.inputs().len() == witnesses.len(), - TransactionCreationError::InvalidWitnessCount + PartiallySignedTransactionCreationError::TxCreationError( + TransactionCreationError::InvalidWitnessCount + ) ); ensure!( tx.inputs().len() == input_utxos.len(), - TransactionCreationError::InvalidInputUtxosCount, + PartiallySignedTransactionCreationError::InvalidInputUtxosCount, ); ensure!( tx.inputs().len() == destinations.len(), - TransactionCreationError::InvalidDestinationsCount + PartiallySignedTransactionCreationError::InvalidDestinationsCount ); let htlc_secrets = htlc_secrets.unwrap_or_else(|| vec![None; tx.inputs().len()]); ensure!( htlc_secrets.len() == tx.inputs().len(), - TransactionCreationError::InvalidHtlcSecretsCount + PartiallySignedTransactionCreationError::InvalidHtlcSecretsCount + ); + + ensure!( + input_utxos.len() == witnesses.len(), + PartiallySignedTransactionCreationError::TxCreationError( + TransactionCreationError::InvalidWitnessCount + ) + ); + + ensure!( + input_utxos.len() == destinations.len(), + PartiallySignedTransactionCreationError::InvalidDestinationsCount + ); + + ensure!( + tx.outputs().len() == output_additional_infos.len(), + PartiallySignedTransactionCreationError::InvalidOutputAdditionalInfoCount ); Ok(Self { @@ -68,6 +143,7 @@ impl PartiallySignedTransaction { input_utxos, destinations, htlc_secrets, + output_additional_infos, }) } @@ -84,7 +160,7 @@ impl PartiallySignedTransaction { self.tx } - pub fn input_utxos(&self) -> &[Option] { + pub fn input_utxos(&self) -> &[Option] { self.input_utxos.as_ref() } @@ -104,6 +180,10 @@ impl PartiallySignedTransaction { self.tx.inputs().len() } + pub fn output_additional_infos(&self) -> &[Option] { + &self.output_additional_infos + } + pub fn all_signatures_available(&self) -> bool { self.witnesses .iter() @@ -118,14 +198,14 @@ impl PartiallySignedTransaction { }) } - pub fn into_signed_tx(self) -> Result { + pub fn into_signed_tx( + self, + ) -> Result { if self.all_signatures_available() { let witnesses = self.witnesses.into_iter().map(|w| w.expect("cannot fail")).collect(); Ok(SignedTransaction::new(self.tx, witnesses)?) } else { - Err(TransactionCreationError::FailedToConvertPartiallySignedTx( - self, - )) + Err(PartiallySignedTransactionCreationError::FailedToConvertPartiallySignedTx(self)) } } } @@ -153,3 +233,5 @@ impl Transactable for PartiallySignedTransaction { self.witnesses.clone() } } + +impl SignatureOnlyVerifiable for PartiallySignedTransaction {} diff --git a/wallet/wallet-cli-commands/src/command_handler/mod.rs b/wallet/wallet-cli-commands/src/command_handler/mod.rs index 496271fef3..7b4e9739fe 100644 --- a/wallet/wallet-cli-commands/src/command_handler/mod.rs +++ b/wallet/wallet-cli-commands/src/command_handler/mod.rs @@ -20,9 +20,8 @@ use std::{fmt::Write, str::FromStr}; use common::{ address::Address, chain::{ - config::checkpoints_data::print_block_heights_ids_as_checkpoints_data, - partially_signed_transaction::PartiallySignedTransaction, ChainConfig, Destination, - SignedTransaction, TxOutput, UtxoOutPoint, + config::checkpoints_data::print_block_heights_ids_as_checkpoints_data, ChainConfig, + Destination, SignedTransaction, TxOutput, UtxoOutPoint, }, primitives::H256, text_summary::TextSummary, @@ -44,10 +43,11 @@ use wallet_rpc_lib::types::{ #[cfg(feature = "trezor")] use wallet_rpc_lib::types::HardwareWalletType; +use wallet_types::partially_signed_transaction::PartiallySignedTransaction; use crate::{ errors::WalletCliCommandError, helper_types::parse_generic_token_transfer, - helper_types::CLIHardwareWalletType, ManageableWalletCommand, WalletManagementCommand, + helper_types::CliHardwareWalletType, ManageableWalletCommand, WalletManagementCommand, }; use self::local_state::WalletWithState; @@ -152,8 +152,8 @@ where } => { let hardware_wallet = hardware_wallet.and_then(|t| match t { #[cfg(feature = "trezor")] - CLIHardwareWalletType::Trezor => Some(HardwareWalletType::Trezor), - CLIHardwareWalletType::None => None, + CliHardwareWalletType::Trezor => Some(HardwareWalletType::Trezor), + CliHardwareWalletType::None => None, }); let newly_generated_mnemonic = self @@ -208,8 +208,8 @@ where } => { let hardware_wallet = hardware_wallet.and_then(|t| match t { #[cfg(feature = "trezor")] - CLIHardwareWalletType::Trezor => Some(HardwareWalletType::Trezor), - CLIHardwareWalletType::None => None, + CliHardwareWalletType::Trezor => Some(HardwareWalletType::Trezor), + CliHardwareWalletType::None => None, }); let newly_generated_mnemonic = self @@ -263,8 +263,8 @@ where } => { let hardware_wallet = hardware_wallet.and_then(|t| match t { #[cfg(feature = "trezor")] - CLIHardwareWalletType::Trezor => Some(HardwareWalletType::Trezor), - CLIHardwareWalletType::None => None, + CliHardwareWalletType::Trezor => Some(HardwareWalletType::Trezor), + CliHardwareWalletType::None => None, }); self.wallet() .await? diff --git a/wallet/wallet-cli-commands/src/helper_types.rs b/wallet/wallet-cli-commands/src/helper_types.rs index c3d7abe044..b484102d96 100644 --- a/wallet/wallet-cli-commands/src/helper_types.rs +++ b/wallet/wallet-cli-commands/src/helper_types.rs @@ -435,7 +435,7 @@ impl YesNo { } #[derive(Debug, Clone, Copy, ValueEnum)] -pub enum CLIHardwareWalletType { +pub enum CliHardwareWalletType { None, #[cfg(feature = "trezor")] Trezor, diff --git a/wallet/wallet-cli-commands/src/lib.rs b/wallet/wallet-cli-commands/src/lib.rs index c21a09a969..6f123ee980 100644 --- a/wallet/wallet-cli-commands/src/lib.rs +++ b/wallet/wallet-cli-commands/src/lib.rs @@ -19,7 +19,7 @@ mod helper_types; pub use command_handler::CommandHandler; pub use errors::WalletCliCommandError; -use helper_types::{CLIHardwareWalletType, YesNo}; +use helper_types::{CliHardwareWalletType, YesNo}; use rpc::description::{Described, Module}; use wallet_rpc_lib::{types::NodeInterface, ColdWalletRpcDescription, WalletRpcDescription}; @@ -66,7 +66,7 @@ pub enum WalletManagementCommand { /// Create a wallet using a connected hardware wallet. Only the public keys will be kept in /// the software wallet #[arg(long, conflicts_with_all(["mnemonic", "passphrase"]))] - hardware_wallet: Option, + hardware_wallet: Option, }, #[clap(name = "wallet-recover")] @@ -91,7 +91,7 @@ pub enum WalletManagementCommand { /// Create a wallet using a connected hardware wallet. Only the public keys will be kept in /// the software wallet #[arg(long, conflicts_with_all(["mnemonic", "passphrase"]))] - hardware_wallet: Option, + hardware_wallet: Option, }, #[clap(name = "wallet-open")] @@ -106,7 +106,7 @@ pub enum WalletManagementCommand { /// Open a wallet file related to a connected hardware wallet. #[arg(long)] - hardware_wallet: Option, + hardware_wallet: Option, }, #[clap(name = "wallet-close")] diff --git a/wallet/wallet-controller/Cargo.toml b/wallet/wallet-controller/Cargo.toml index 04741d42d9..a1a0c055ec 100644 --- a/wallet/wallet-controller/Cargo.toml +++ b/wallet/wallet-controller/Cargo.toml @@ -22,6 +22,7 @@ randomness = { path = "../../randomness" } serialization = { path = "../../serialization" } storage = { path = "../../storage" } storage-inmemory = { path = "../../storage/inmemory" } +pos-accounting = { path = "../../pos-accounting" } utils = { path = "../../utils" } utils-networking = { path = "../../utils/networking" } wallet = { path = ".." } diff --git a/wallet/wallet-controller/src/helpers.rs b/wallet/wallet-controller/src/helpers.rs new file mode 100644 index 0000000000..b8a25aee1e --- /dev/null +++ b/wallet/wallet-controller/src/helpers.rs @@ -0,0 +1,296 @@ +// Copyright (c) 2023 RBB S.r.l +// opensource@mintlayer.org +// SPDX-License-Identifier: MIT +// Licensed under the MIT License; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Common code for read and synced controllers + +use std::collections::BTreeMap; + +use common::{ + address::RpcAddress, + chain::{ + output_value::OutputValue, + tokens::{RPCTokenInfo, TokenId}, + ChainConfig, Destination, PoolId, Transaction, TxInput, TxOutput, UtxoOutPoint, + }, + primitives::{amount::RpcAmountOut, Amount}, +}; +use futures::{ + stream::{FuturesOrdered, FuturesUnordered}, + TryStreamExt, +}; +use node_comm::node_traits::NodeInterface; +use wallet::{ + account::currency_grouper::Currency, + destination_getters::{get_tx_output_destination, HtlcSpendingCondition}, + WalletError, +}; +use wallet_types::partially_signed_transaction::{ + PartiallySignedTransaction, TokenAdditionalInfo, UtxoAdditionalInfo, UtxoWithAdditionalInfo, +}; + +use crate::{runtime_wallet::RuntimeWallet, types::Balances, ControllerError}; + +pub async fn fetch_token_info( + rpc_client: &T, + token_id: TokenId, +) -> Result> { + rpc_client + .get_token_info(token_id) + .await + .map_err(ControllerError::NodeCallError)? + .ok_or(ControllerError::WalletError(WalletError::UnknownTokenId( + token_id, + ))) +} + +pub async fn fetch_utxo( + rpc_client: &T, + input: &UtxoOutPoint, + wallet: &RuntimeWallet, +) -> Result> { + // search locally for the unspent utxo + if let Some(out) = match &wallet { + RuntimeWallet::Software(w) => w.find_unspent_utxo_with_destination(input), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.find_unspent_utxo_with_destination(input), + } { + return Ok(out.0); + } + + // check the chainstate + rpc_client + .get_utxo(input.clone()) + .await + .map_err(ControllerError::NodeCallError)? + .ok_or(ControllerError::WalletError(WalletError::CannotFindUtxo( + input.clone(), + ))) +} + +pub async fn fetch_utxo_with_destination( + rpc_client: &T, + input: &UtxoOutPoint, + wallet: &RuntimeWallet, +) -> Result<(TxOutput, Destination), ControllerError> { + // search locally for the unspent utxo + if let Some(out) = match &wallet { + RuntimeWallet::Software(w) => w.find_unspent_utxo_with_destination(input), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.find_unspent_utxo_with_destination(input), + } { + return Ok(out); + } + + // check the chainstate + let utxo = rpc_client + .get_utxo(input.clone()) + .await + .map_err(ControllerError::NodeCallError)? + .ok_or(ControllerError::WalletError(WalletError::CannotFindUtxo( + input.clone(), + )))?; + + let pool_id = pool_id_from_txo(&utxo); + let dest = if let Some(pool_id) = pool_id { + rpc_client + .get_pool_decommission_destination(pool_id) + .await + .map_err(ControllerError::NodeCallError)? + } else { + get_tx_output_destination(&utxo, &|_| None, HtlcSpendingCondition::Skip) + } + .ok_or(ControllerError::WalletError(WalletError::CannotFindUtxo( + input.clone(), + )))?; + + Ok((utxo, dest)) +} + +fn pool_id_from_txo(utxo: &TxOutput) -> Option { + match utxo { + TxOutput::CreateStakePool(pool_id, _) | TxOutput::ProduceBlockFromStake(_, pool_id) => { + Some(*pool_id) + } + TxOutput::Burn(_) + | TxOutput::Transfer(_, _) + | TxOutput::LockThenTransfer(_, _, _) + | TxOutput::Htlc(_, _) + | TxOutput::AnyoneCanTake(_) + | TxOutput::IssueNft(_, _, _) + | TxOutput::IssueFungibleToken(_) + | TxOutput::DelegateStaking(_, _) + | TxOutput::CreateDelegationId(_, _) + | TxOutput::DataDeposit(_) => None, + } +} + +async fn fetch_token_extra_info( + rpc_client: &T, + value: &OutputValue, +) -> Result, ControllerError> +where + T: NodeInterface, +{ + match value { + OutputValue::Coin(_) | OutputValue::TokenV0(_) => Ok(None), + OutputValue::TokenV1(token_id, _) => { + let info = fetch_token_info(rpc_client, *token_id).await?; + Ok(Some(TokenAdditionalInfo { + num_decimals: info.token_number_of_decimals(), + ticker: info.token_ticker().to_vec(), + })) + } + } +} + +pub async fn fetch_utxo_extra_info( + rpc_client: &T, + utxo: TxOutput, +) -> Result> +where + T: NodeInterface, +{ + match &utxo { + TxOutput::Burn(value) + | TxOutput::Transfer(value, _) + | TxOutput::LockThenTransfer(value, _, _) + | TxOutput::Htlc(value, _) => { + let additional_info = fetch_token_extra_info(rpc_client, value) + .await? + .map(UtxoAdditionalInfo::TokenInfo); + Ok(UtxoWithAdditionalInfo::new(utxo, additional_info)) + } + TxOutput::AnyoneCanTake(order) => { + let ask = fetch_token_extra_info(rpc_client, order.ask()).await?; + let give = fetch_token_extra_info(rpc_client, order.ask()).await?; + let additional_info = Some(UtxoAdditionalInfo::AnyoneCanTake { ask, give }); + Ok(UtxoWithAdditionalInfo::new(utxo, additional_info)) + } + TxOutput::ProduceBlockFromStake(_, pool_id) => { + let staker_balance = rpc_client + .get_staker_balance(*pool_id) + .await + .map_err(ControllerError::NodeCallError)? + .ok_or(WalletError::UnknownPoolId(*pool_id))?; + Ok(UtxoWithAdditionalInfo::new( + utxo, + Some(UtxoAdditionalInfo::PoolInfo { staker_balance }), + )) + } + TxOutput::IssueNft(_, _, _) + | TxOutput::IssueFungibleToken(_) + | TxOutput::CreateStakePool(_, _) + | TxOutput::DelegateStaking(_, _) + | TxOutput::CreateDelegationId(_, _) + | TxOutput::DataDeposit(_) => Ok(UtxoWithAdditionalInfo::new(utxo, None)), + } +} + +pub async fn into_balances( + rpc_client: &T, + chain_config: &ChainConfig, + mut balances: BTreeMap, +) -> Result> { + let coins = balances.remove(&Currency::Coin).unwrap_or(Amount::ZERO); + let coins = RpcAmountOut::from_amount_no_padding(coins, chain_config.coin_decimals()); + + let tasks: FuturesUnordered<_> = balances + .into_iter() + .map(|(currency, amount)| async move { + let token_id = match currency { + Currency::Coin => panic!("Removed just above"), + Currency::Token(token_id) => token_id, + }; + + fetch_token_info(rpc_client, token_id).await.map(|info| { + let decimals = info.token_number_of_decimals(); + let amount = RpcAmountOut::from_amount_no_padding(amount, decimals); + let token_id = RpcAddress::new(chain_config, token_id).expect("addressable"); + (token_id, amount) + }) + }) + .collect(); + + Ok(Balances::new(coins, tasks.try_collect().await?)) +} + +pub async fn tx_to_partially_signed_tx( + rpc_client: &T, + wallet: &RuntimeWallet, + tx: Transaction, +) -> Result> { + let tasks: FuturesOrdered<_> = tx + .inputs() + .iter() + .map(|inp| into_utxo_and_destination(rpc_client, wallet, inp)) + .collect(); + let (input_utxos, destinations) = tasks.try_collect::>().await?.into_iter().unzip(); + let num_inputs = tx.inputs().len(); + + let tasks: FuturesOrdered<_> = tx + .outputs() + .iter() + .map(|out| fetch_utxo_extra_info(rpc_client, out.clone())) + .collect(); + let output_additional_infos = tasks + .try_collect::>() + .await? + .into_iter() + .map(|x| x.additional_info) + .collect(); + + let ptx = PartiallySignedTransaction::new( + tx, + vec![None; num_inputs], + input_utxos, + destinations, + None, + output_additional_infos, + ) + .map_err(WalletError::PartiallySignedTransactionCreation)?; + Ok(ptx) +} + +async fn into_utxo_and_destination( + rpc_client: &T, + wallet: &RuntimeWallet, + tx_inp: &TxInput, +) -> Result<(Option, Option), ControllerError> { + Ok(match tx_inp { + TxInput::Utxo(outpoint) => { + let (utxo, dest) = fetch_utxo_with_destination(rpc_client, outpoint, wallet).await?; + let utxo_with_extra_info = fetch_utxo_extra_info(rpc_client, utxo).await?; + (Some(utxo_with_extra_info), Some(dest)) + } + TxInput::Account(acc_outpoint) => { + // find delegation destination + let dest = match &wallet { + RuntimeWallet::Software(w) => w.find_account_destination(acc_outpoint), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.find_account_destination(acc_outpoint), + }; + (None, dest) + } + TxInput::AccountCommand(_, cmd) => { + // find authority of the token + let dest = match &wallet { + RuntimeWallet::Software(w) => w.find_account_command_destination(cmd), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.find_account_command_destination(cmd), + }; + (None, dest) + } + }) +} diff --git a/wallet/wallet-controller/src/lib.rs b/wallet/wallet-controller/src/lib.rs index a91b59e2b3..ca300b42e0 100644 --- a/wallet/wallet-controller/src/lib.rs +++ b/wallet/wallet-controller/src/lib.rs @@ -15,8 +15,10 @@ //! Common code for wallet UI applications +mod helpers; pub mod mnemonic; pub mod read; +mod runtime_wallet; mod sync; pub mod synced_controller; pub mod types; @@ -28,12 +30,10 @@ use blockprod::BlockProductionError; use chainstate::tx_verifier::{ self, error::ScriptError, input_check::signature_only_check::SignatureOnlyVerifiable, }; -use futures::{ - never::Never, - stream::{FuturesOrdered, FuturesUnordered}, - TryStreamExt, -}; +use futures::{never::Never, stream::FuturesOrdered, TryStreamExt}; +use helpers::{fetch_token_info, fetch_utxo, fetch_utxo_extra_info, into_balances}; use node_comm::rpc_client::ColdWalletClient; +use runtime_wallet::RuntimeWallet; use std::{ collections::{BTreeMap, BTreeSet}, fs, @@ -54,18 +54,16 @@ use sync::InSync; use synced_controller::SyncedController; use common::{ - address::{AddressError, RpcAddress}, + address::AddressError, chain::{ block::timestamp::BlockTimestamp, htlc::HtlcSecret, - partially_signed_transaction::PartiallySignedTransaction, signature::{inputsig::InputWitness, DestinationSigError, Transactable}, tokens::{RPCTokenInfo, TokenId}, Block, ChainConfig, Destination, GenBlock, PoolId, SignedTransaction, Transaction, TxInput, TxOutput, UtxoOutPoint, }, primitives::{ - amount::RpcAmountOut, time::{get_time, Time}, Amount, BlockHeight, Id, Idable, }, @@ -94,7 +92,7 @@ use wallet::{ signer::software_signer::SoftwareSignerProvider, wallet::WalletPoolsFilter, wallet_events::WalletEvents, - Wallet, WalletError, WalletResult, + WalletError, WalletResult, }; pub use wallet_types::{ @@ -102,6 +100,7 @@ pub use wallet_types::{ utxo_types::{UtxoState, UtxoStates, UtxoType, UtxoTypes}, }; use wallet_types::{ + partially_signed_transaction::{PartiallySignedTransaction, UtxoWithAdditionalInfo}, signature_status::SignatureStatus, wallet_type::{WalletControllerMode, WalletType}, with_locked::WithLocked, @@ -151,8 +150,6 @@ pub enum ControllerError { InvalidTxOutput(GenericCurrencyTransferToTxOutputConversionError), #[error("The specified token {0} is not a fungible token")] NotFungibleToken(TokenId), - #[error("Unsupported operation for a Hardware wallet")] - UnsupportedHardwareWalletOperation, } #[derive(Clone, Copy)] @@ -167,18 +164,12 @@ pub struct ControllerConfig { pub broadcast_to_mempool: bool, } -pub enum WalletType2 { - Software(Wallet), - #[cfg(feature = "trezor")] - Trezor(Wallet), -} - pub struct Controller { chain_config: Arc, rpc_client: T, - wallet: WalletType2, + wallet: RuntimeWallet, staking_started: BTreeSet, @@ -204,7 +195,7 @@ where pub async fn new( chain_config: Arc, rpc_client: T, - wallet: WalletType2, + wallet: RuntimeWallet, wallet_events: W, ) -> Result> { let mut controller = Self { @@ -224,7 +215,7 @@ where pub fn new_unsynced( chain_config: Arc, rpc_client: T, - wallet: WalletType2, + wallet: RuntimeWallet, wallet_events: W, ) -> Self { Self { @@ -242,7 +233,7 @@ where args: WalletTypeArgsComputed, best_block: (BlockHeight, Id), wallet_type: WalletType, - ) -> Result, ControllerError> { + ) -> Result, ControllerError> { utils::ensure!( !file_path.as_ref().exists(), ControllerError::WalletFileError( @@ -277,7 +268,7 @@ where }, ) .map_err(ControllerError::WalletError)?; - Ok(WalletType2::Software(wallet)) + Ok(RuntimeWallet::Software(wallet)) } #[cfg(feature = "trezor")] WalletTypeArgsComputed::Trezor => { @@ -289,7 +280,7 @@ where |_db_tx| Ok(TrezorSignerProvider::new().map_err(SignerError::TrezorError)?), ) .map_err(ControllerError::WalletError)?; - Ok(WalletType2::Trezor(wallet)) + Ok(RuntimeWallet::Trezor(wallet)) } } } @@ -299,7 +290,7 @@ where file_path: impl AsRef, args: WalletTypeArgsComputed, wallet_type: WalletType, - ) -> Result, ControllerError> { + ) -> Result, ControllerError> { utils::ensure!( !file_path.as_ref().exists(), ControllerError::WalletFileError( @@ -334,7 +325,7 @@ where }, ) .map_err(ControllerError::WalletError)?; - Ok(WalletType2::Software(wallet)) + Ok(RuntimeWallet::Software(wallet)) } #[cfg(feature = "trezor")] WalletTypeArgsComputed::Trezor => { @@ -345,7 +336,7 @@ where |_db_tx| Ok(TrezorSignerProvider::new().map_err(SignerError::TrezorError)?), ) .map_err(ControllerError::WalletError)?; - Ok(WalletType2::Trezor(wallet)) + Ok(RuntimeWallet::Trezor(wallet)) } } } @@ -384,7 +375,7 @@ where current_controller_mode: WalletControllerMode, force_change_wallet_type: bool, open_as_wallet_type: WalletType, - ) -> Result, ControllerError> { + ) -> Result, ControllerError> { utils::ensure!( file_path.as_ref().exists(), ControllerError::WalletFileError( @@ -408,7 +399,7 @@ where |db_tx| SoftwareSignerProvider::load_from_database(chain_config.clone(), db_tx), ) .map_err(ControllerError::WalletError)?; - Ok(WalletType2::Software(wallet)) + Ok(RuntimeWallet::Software(wallet)) } #[cfg(feature = "trezor")] WalletType::Trezor => { @@ -422,41 +413,30 @@ where |db_tx| TrezorSignerProvider::load_from_database(chain_config.clone(), db_tx), ) .map_err(ControllerError::WalletError)?; - Ok(WalletType2::Trezor(wallet)) + Ok(RuntimeWallet::Trezor(wallet)) } } } pub fn seed_phrase(&self) -> Result, ControllerError> { - match &self.wallet { - WalletType2::Software(w) => w.seed_phrase(), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.seed_phrase(), - } - .map(|opt| opt.map(SeedWithPassPhrase::from_serializable_seed_phrase)) - .map_err(ControllerError::WalletError) + self.wallet + .seed_phrase() + .map(|opt| opt.map(SeedWithPassPhrase::from_serializable_seed_phrase)) + .map_err(ControllerError::WalletError) } /// Delete the seed phrase if stored in the database pub fn delete_seed_phrase(&self) -> Result, ControllerError> { - match &self.wallet { - WalletType2::Software(w) => w.delete_seed_phrase(), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.delete_seed_phrase(), - } - .map(|opt| opt.map(SeedWithPassPhrase::from_serializable_seed_phrase)) - .map_err(ControllerError::WalletError) + self.wallet + .delete_seed_phrase() + .map(|opt| opt.map(SeedWithPassPhrase::from_serializable_seed_phrase)) + .map_err(ControllerError::WalletError) } /// Rescan the blockchain /// Resets the wallet to the genesis block pub fn reset_wallet_to_genesis(&mut self) -> Result<(), ControllerError> { - match &mut self.wallet { - WalletType2::Software(w) => w.reset_wallet_to_genesis(), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.reset_wallet_to_genesis(), - } - .map_err(ControllerError::WalletError) + self.wallet.reset_wallet_to_genesis().map_err(ControllerError::WalletError) } /// Encrypts the wallet using the specified `password`, or removes the existing encryption if `password` is `None`. @@ -469,12 +449,7 @@ where /// /// This method returns an error if the wallet is locked pub fn encrypt_wallet(&mut self, password: &Option) -> Result<(), ControllerError> { - match &mut self.wallet { - WalletType2::Software(w) => w.encrypt_wallet(password), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.encrypt_wallet(password), - } - .map_err(ControllerError::WalletError) + self.wallet.encrypt_wallet(password).map_err(ControllerError::WalletError) } /// Unlocks the wallet using the specified password. @@ -487,12 +462,7 @@ where /// /// This method returns an error if the password is incorrect pub fn unlock_wallet(&mut self, password: &String) -> Result<(), ControllerError> { - match &mut self.wallet { - WalletType2::Software(w) => w.unlock_wallet(password), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.unlock_wallet(password), - } - .map_err(ControllerError::WalletError) + self.wallet.unlock_wallet(password).map_err(ControllerError::WalletError) } /// Locks the wallet by making the encrypted private keys inaccessible. @@ -505,12 +475,7 @@ where self.staking_started.is_empty(), ControllerError::StakingRunning ); - match &mut self.wallet { - WalletType2::Software(w) => w.lock_wallet(), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.lock_wallet(), - } - .map_err(ControllerError::WalletError) + self.wallet.lock_wallet().map_err(ControllerError::WalletError) } /// Sets the lookahead size for key generation @@ -525,20 +490,13 @@ where ) -> Result<(), ControllerError> { utils::ensure!(lookahead_size > 0, ControllerError::InvalidLookaheadSize); - match &mut self.wallet { - WalletType2::Software(w) => w.set_lookahead_size(lookahead_size, force_reduce), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.set_lookahead_size(lookahead_size, force_reduce), - } - .map_err(ControllerError::WalletError) + self.wallet + .set_lookahead_size(lookahead_size, force_reduce) + .map_err(ControllerError::WalletError) } pub fn wallet_info(&self) -> WalletInfo { - let (wallet_id, account_names) = match &self.wallet { - WalletType2::Software(w) => w.wallet_info(), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.wallet_info(), - }; + let (wallet_id, account_names) = self.wallet.wallet_info(); WalletInfo { wallet_id, account_names, @@ -567,14 +525,10 @@ where transaction_ids: Vec>, packing_strategy: PackingStrategy, ) -> Result> { - let pos_data = match &self.wallet { - WalletType2::Software(w) => w.get_pos_gen_block_data(account_index, pool_id), - #[cfg(feature = "trezor")] - WalletType2::Trezor(_) => { - return Err(ControllerError::UnsupportedHardwareWalletOperation) - } - } - .map_err(ControllerError::WalletError)?; + let pos_data = self + .wallet + .get_pos_gen_block_data(account_index, pool_id) + .map_err(ControllerError::WalletError)?; let public_key = self .rpc_client @@ -611,12 +565,10 @@ where transaction_ids: Vec>, packing_strategy: PackingStrategy, ) -> Result> { - let pools = match &self.wallet { - WalletType2::Software(w) => w.get_pool_ids(account_index, WalletPoolsFilter::Stake), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_pool_ids(account_index, WalletPoolsFilter::Stake), - } - .map_err(ControllerError::WalletError)?; + let pools = self + .wallet + .get_pool_ids(account_index, WalletPoolsFilter::Stake) + .map_err(ControllerError::WalletError)?; let mut last_error = ControllerError::NoStakingPool; for (pool_id, _) in pools { @@ -682,14 +634,10 @@ where seconds_to_check_for_height: u64, check_all_timestamps_between_blocks: bool, ) -> Result>, ControllerError> { - let pos_data = match &self.wallet { - WalletType2::Software(w) => w.get_pos_gen_block_data_by_pool_id(pool_id), - #[cfg(feature = "trezor")] - WalletType2::Trezor(_) => { - return Err(ControllerError::UnsupportedHardwareWalletOperation) - } - } - .map_err(ControllerError::WalletError)?; + let pos_data = self + .wallet + .get_pos_gen_block_data_by_pool_id(pool_id) + .map_err(ControllerError::WalletError)?; let input_data = PoSTimestampSearchInputData::new(pool_id, pos_data.vrf_private_key().clone()); @@ -715,12 +663,7 @@ where &mut self, name: Option, ) -> Result<(U31, Option), ControllerError> { - match &mut self.wallet { - WalletType2::Software(w) => w.create_next_account(name), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.create_next_account(name), - } - .map_err(ControllerError::WalletError) + self.wallet.create_next_account(name).map_err(ControllerError::WalletError) } pub fn update_account_name( @@ -728,12 +671,9 @@ where account_index: U31, name: Option, ) -> Result<(U31, Option), ControllerError> { - match &mut self.wallet { - WalletType2::Software(w) => w.set_account_name(account_index, name), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.set_account_name(account_index, name), - } - .map_err(ControllerError::WalletError) + self.wallet + .set_account_name(account_index, name) + .map_err(ControllerError::WalletError) } pub fn stop_staking(&mut self, account_index: U31) -> Result<(), ControllerError> { @@ -747,37 +687,28 @@ where } pub fn best_block(&self) -> (Id, BlockHeight) { - *match &self.wallet { - WalletType2::Software(w) => w.get_best_block(), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_best_block(), - } - .values() - .min_by_key(|(_block_id, block_height)| block_height) - .expect("there must be at least one account") + *self + .wallet + .get_best_block() + .values() + .min_by_key(|(_block_id, block_height)| block_height) + .expect("there must be at least one account") } pub async fn get_stake_pool_balances( &self, account_index: U31, ) -> Result, ControllerError> { - let stake_pool_utxos = match &self.wallet { - WalletType2::Software(w) => w.get_utxos( - account_index, - UtxoType::CreateStakePool | UtxoType::ProduceBlockFromStake, - UtxoState::Confirmed.into(), - WithLocked::Unlocked, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_utxos( + let stake_pool_utxos = self + .wallet + .get_utxos( account_index, UtxoType::CreateStakePool | UtxoType::ProduceBlockFromStake, UtxoState::Confirmed.into(), WithLocked::Unlocked, - ), - } - .map_err(ControllerError::WalletError)?; - let pool_ids = stake_pool_utxos.into_iter().filter_map(|(_, utxo, _)| match utxo { + ) + .map_err(ControllerError::WalletError)?; + let pool_ids = stake_pool_utxos.into_iter().filter_map(|(_, utxo)| match utxo { TxOutput::ProduceBlockFromStake(_, pool_id) | TxOutput::CreateStakePool(pool_id, _) => { Some(pool_id) } @@ -809,11 +740,11 @@ where /// Synchronize the wallet to the current node tip height and return pub async fn sync_once(&mut self) -> Result<(), ControllerError> { let res = match &mut self.wallet { - WalletType2::Software(w) => { + RuntimeWallet::Software(w) => { sync::sync_once(&self.chain_config, &self.rpc_client, w, &self.wallet_events).await } #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => { + RuntimeWallet::Trezor(w) => { sync::sync_once(&self.chain_config, &self.rpc_client, w, &self.wallet_events).await } }?; @@ -826,12 +757,12 @@ where pub async fn try_sync_once(&mut self) -> Result<(), ControllerError> { match &mut self.wallet { - WalletType2::Software(w) => { + RuntimeWallet::Software(w) => { sync::sync_once(&self.chain_config, &self.rpc_client, w, &self.wallet_events) .await?; } #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => { + RuntimeWallet::Trezor(w) => { sync::sync_once(&self.chain_config, &self.rpc_client, w, &self.wallet_events) .await?; } @@ -967,9 +898,14 @@ where &self, ptx: PartiallySignedTransaction, ) -> Result> { - let input_utxos: Vec<_> = ptx.input_utxos().iter().flatten().cloned().collect(); + let input_utxos: Vec<_> = + ptx.input_utxos().iter().flatten().map(|utxo| &utxo.utxo).cloned().collect(); let fees = self.get_fees(&input_utxos, ptx.tx().outputs()).await?; - let inputs_utxos_refs: Vec<_> = ptx.input_utxos().iter().map(|out| out.as_ref()).collect(); + let inputs_utxos_refs: Vec<_> = ptx + .input_utxos() + .iter() + .map(|out| out.as_ref().map(|utxo| &utxo.utxo)) + .collect(); let signature_statuses: Vec<_> = ptx .witnesses() .iter() @@ -1106,14 +1042,22 @@ where .collect::, WalletError>>() .map_err(ControllerError::WalletError)?; + let input_utxos = self.fetch_utxos_extra_info(input_utxos).await?; + let output_additional_infos = self + .fetch_utxos_extra_info(tx.outputs().to_vec()) + .await? + .into_iter() + .map(|x| x.additional_info) + .collect(); let tx = PartiallySignedTransaction::new( tx, vec![None; num_inputs], input_utxos.into_iter().map(Option::Some).collect(), destinations.into_iter().map(Option::Some).collect(), htlc_secrets, + output_additional_infos, ) - .map_err(WalletError::TransactionCreation)?; + .map_err(WalletError::PartiallySignedTransactionCreation)?; TransactionToSign::Partial(tx) }; @@ -1186,29 +1130,24 @@ where &self, inputs: &[UtxoOutPoint], ) -> Result, ControllerError> { - let tasks: FuturesOrdered<_> = inputs.iter().map(|input| self.fetch_utxo(input)).collect(); + let tasks: FuturesOrdered<_> = inputs + .iter() + .map(|input| fetch_utxo(&self.rpc_client, input, &self.wallet)) + .collect(); let input_utxos: Vec = tasks.try_collect().await?; Ok(input_utxos) } - async fn fetch_utxo(&self, input: &UtxoOutPoint) -> Result> { - // search locally for the unspent utxo - if let Some(out) = match &self.wallet { - WalletType2::Software(w) => w.find_unspent_utxo_with_destination(input), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.find_unspent_utxo_with_destination(input), - } { - return Ok(out.0); - } - - // check the chainstate - self.rpc_client - .get_utxo(input.clone()) - .await - .map_err(ControllerError::NodeCallError)? - .ok_or(ControllerError::WalletError(WalletError::CannotFindUtxo( - input.clone(), - ))) + async fn fetch_utxos_extra_info( + &self, + inputs: Vec, + ) -> Result, ControllerError> { + let tasks: FuturesOrdered<_> = inputs + .into_iter() + .map(|input| fetch_utxo_extra_info(&self.rpc_client, input)) + .collect(); + let input_utxos: Vec = tasks.try_collect().await?; + Ok(input_utxos) } async fn fetch_opt_utxo( @@ -1216,7 +1155,7 @@ where input: &TxInput, ) -> Result, ControllerError> { match input { - TxInput::Utxo(utxo) => self.fetch_utxo(utxo).await.map(Some), + TxInput::Utxo(utxo) => fetch_utxo(&self.rpc_client, utxo, &self.wallet).await.map(Some), TxInput::Account(_) => Ok(None), TxInput::AccountCommand(_, _) => Ok(None), } @@ -1272,11 +1211,7 @@ where /// Rebroadcast not confirmed transactions async fn rebroadcast_txs(&mut self, rebroadcast_txs_again_at: &mut Time) { if get_time() >= *rebroadcast_txs_again_at { - let txs = match &self.wallet { - WalletType2::Software(w) => w.get_transactions_to_be_broadcast(), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_transactions_to_be_broadcast(), - }; + let txs = self.wallet.get_transactions_to_be_broadcast(); match txs { Err(error) => { log::error!("Fetching transactions for rebroadcasting failed: {error}"); @@ -1299,44 +1234,3 @@ where } } } - -pub async fn fetch_token_info( - rpc_client: &T, - token_id: TokenId, -) -> Result> { - rpc_client - .get_token_info(token_id) - .await - .map_err(ControllerError::NodeCallError)? - .ok_or(ControllerError::WalletError(WalletError::UnknownTokenId( - token_id, - ))) -} - -pub async fn into_balances( - rpc_client: &T, - chain_config: &ChainConfig, - mut balances: BTreeMap, -) -> Result> { - let coins = balances.remove(&Currency::Coin).unwrap_or(Amount::ZERO); - let coins = RpcAmountOut::from_amount_no_padding(coins, chain_config.coin_decimals()); - - let tasks: FuturesUnordered<_> = balances - .into_iter() - .map(|(currency, amount)| async move { - let token_id = match currency { - Currency::Coin => panic!("Removed just above"), - Currency::Token(token_id) => token_id, - }; - - fetch_token_info(rpc_client, token_id).await.map(|info| { - let decimals = info.token_number_of_decimals(); - let amount = RpcAmountOut::from_amount_no_padding(amount, decimals); - let token_id = RpcAddress::new(chain_config, token_id).expect("addressable"); - (token_id, amount) - }) - }) - .collect(); - - Ok(Balances::new(coins, tasks.try_collect().await?)) -} diff --git a/wallet/wallet-controller/src/read.rs b/wallet/wallet-controller/src/read.rs index 56860dfa4c..3336f14053 100644 --- a/wallet/wallet-controller/src/read.rs +++ b/wallet/wallet-controller/src/read.rs @@ -45,12 +45,13 @@ use wallet_types::{ }; use crate::{ + runtime_wallet::RuntimeWallet, types::{AccountStandaloneKeyDetails, Balances, CreatedBlockInfo}, - ControllerError, WalletType2, + ControllerError, }; pub struct ReadOnlyController<'a, T, B: storage::Backend + 'static> { - wallet: &'a WalletType2, + wallet: &'a RuntimeWallet, rpc_client: T, chain_config: &'a ChainConfig, account_index: U31, @@ -65,7 +66,7 @@ where B: storage::Backend + 'static, { pub fn new( - wallet: &'a WalletType2, + wallet: &'a RuntimeWallet, rpc_client: T, chain_config: &'a ChainConfig, account_index: U31, @@ -87,12 +88,9 @@ where utxo_states: UtxoStates, with_locked: WithLocked, ) -> Result, ControllerError> { - match &self.wallet { - WalletType2::Software(w) => w.get_balance(self.account_index, utxo_states, with_locked), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_balance(self.account_index, utxo_states, with_locked), - } - .map_err(ControllerError::WalletError) + self.wallet + .get_balance(self.account_index, utxo_states, with_locked) + .map_err(ControllerError::WalletError) } pub async fn get_decimal_balance( @@ -110,17 +108,10 @@ where utxo_states: UtxoStates, with_locked: WithLocked, ) -> Result, ControllerError> { - match &self.wallet { - WalletType2::Software(w) => { - w.get_multisig_utxos(self.account_index, utxo_types, utxo_states, with_locked) - } - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => { - w.get_multisig_utxos(self.account_index, utxo_types, utxo_states, with_locked) - } - } - .map(|utxos| utxos.into_iter().map(|(outpoint, output, _)| (outpoint, output)).collect()) - .map_err(ControllerError::WalletError) + self.wallet + .get_multisig_utxos(self.account_index, utxo_types, utxo_states, with_locked) + .map(|utxos| utxos.into_iter().collect()) + .map_err(ControllerError::WalletError) } pub fn get_utxos( @@ -129,26 +120,15 @@ where utxo_states: UtxoStates, with_locked: WithLocked, ) -> Result, ControllerError> { - match &self.wallet { - WalletType2::Software(w) => { - w.get_utxos(self.account_index, utxo_types, utxo_states, with_locked) - } - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => { - w.get_utxos(self.account_index, utxo_types, utxo_states, with_locked) - } - } - .map(|utxos| utxos.into_iter().map(|(outpoint, output, _)| (outpoint, output)).collect()) - .map_err(ControllerError::WalletError) + self.wallet + .get_utxos(self.account_index, utxo_types, utxo_states, with_locked) + .map_err(ControllerError::WalletError) } pub fn pending_transactions(&self) -> Result>, ControllerError> { - match &self.wallet { - WalletType2::Software(w) => w.pending_transactions(self.account_index), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.pending_transactions(self.account_index), - } - .map_err(ControllerError::WalletError) + self.wallet + .pending_transactions(self.account_index) + .map_err(ControllerError::WalletError) } pub fn mainchain_transactions( @@ -156,16 +136,9 @@ where destination: Option, limit: usize, ) -> Result, ControllerError> { - match &self.wallet { - WalletType2::Software(w) => { - w.mainchain_transactions(self.account_index, destination, limit) - } - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => { - w.mainchain_transactions(self.account_index, destination, limit) - } - } - .map_err(ControllerError::WalletError) + self.wallet + .mainchain_transactions(self.account_index, destination, limit) + .map_err(ControllerError::WalletError) } pub fn get_transaction_list( @@ -173,68 +146,46 @@ where skip: usize, count: usize, ) -> Result> { - match &self.wallet { - WalletType2::Software(w) => w.get_transaction_list(self.account_index, skip, count), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_transaction_list(self.account_index, skip, count), - } - .map_err(ControllerError::WalletError) + self.wallet + .get_transaction_list(self.account_index, skip, count) + .map_err(ControllerError::WalletError) } pub fn get_transaction( &self, transaction_id: Id, ) -> Result<&TxData, ControllerError> { - match &self.wallet { - WalletType2::Software(w) => w.get_transaction(self.account_index, transaction_id), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_transaction(self.account_index, transaction_id), - } - .map_err(ControllerError::WalletError) + self.wallet + .get_transaction(self.account_index, transaction_id) + .map_err(ControllerError::WalletError) } pub fn get_all_issued_addresses( &self, ) -> Result>, ControllerError> { - match &self.wallet { - WalletType2::Software(w) => w.get_all_issued_addresses(self.account_index), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_all_issued_addresses(self.account_index), - } - .map_err(ControllerError::WalletError) + self.wallet + .get_all_issued_addresses(self.account_index) + .map_err(ControllerError::WalletError) } pub fn get_all_issued_vrf_public_keys( &self, ) -> Result, ControllerError> { - match &self.wallet { - WalletType2::Software(w) => w.get_all_issued_vrf_public_keys(self.account_index), - #[cfg(feature = "trezor")] - WalletType2::Trezor(_) => { - return Err(ControllerError::UnsupportedHardwareWalletOperation) - } - } - .map_err(ControllerError::WalletError) + self.wallet + .get_all_issued_vrf_public_keys(self.account_index) + .map_err(ControllerError::WalletError) } pub fn get_legacy_vrf_public_key(&self) -> Result, ControllerError> { - match &self.wallet { - WalletType2::Software(w) => w.get_legacy_vrf_public_key(self.account_index), - #[cfg(feature = "trezor")] - WalletType2::Trezor(_) => { - return Err(ControllerError::UnsupportedHardwareWalletOperation) - } - } - .map_err(ControllerError::WalletError) + self.wallet + .get_legacy_vrf_public_key(self.account_index) + .map_err(ControllerError::WalletError) } pub fn get_addresses_usage(&self) -> Result<&'a KeychainUsageState, ControllerError> { - match &self.wallet { - WalletType2::Software(w) => w.get_addresses_usage(self.account_index), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_addresses_usage(self.account_index), - } - .map_err(ControllerError::WalletError) + self.wallet + .get_addresses_usage(self.account_index) + .map_err(ControllerError::WalletError) } /// Get all addresses with usage information @@ -259,12 +210,9 @@ where /// Get all standalone addresses with their labels pub fn get_standalone_addresses(&self) -> Result> { - match &self.wallet { - WalletType2::Software(w) => w.get_all_standalone_addresses(self.account_index), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_all_standalone_addresses(self.account_index), - } - .map_err(ControllerError::WalletError) + self.wallet + .get_all_standalone_addresses(self.account_index) + .map_err(ControllerError::WalletError) } /// Get all standalone addresses with their labels and balances @@ -272,16 +220,10 @@ where &self, address: Destination, ) -> Result> { - let (address, balances, details) = match &self.wallet { - WalletType2::Software(w) => { - w.get_all_standalone_address_details(self.account_index, address) - } - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => { - w.get_all_standalone_address_details(self.account_index, address) - } - } - .map_err(ControllerError::WalletError)?; + let (address, balances, details) = self + .wallet + .get_all_standalone_address_details(self.account_index, address) + .map_err(ControllerError::WalletError)?; let balances = super::into_balances(&self.rpc_client, self.chain_config, balances).await?; @@ -321,12 +263,10 @@ where &self, filter: WalletPoolsFilter, ) -> Result, ControllerError> { - let pools = match &self.wallet { - WalletType2::Software(w) => w.get_pool_ids(self.account_index, filter), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_pool_ids(self.account_index, filter), - } - .map_err(ControllerError::WalletError)?; + let pools = self + .wallet + .get_pool_ids(self.account_index, filter) + .map_err(ControllerError::WalletError)?; let tasks: FuturesUnordered<_> = pools .into_iter() @@ -372,7 +312,7 @@ where &self, ) -> Result, ControllerError> { let delegations = match &self.wallet { - WalletType2::Software(w) => { + RuntimeWallet::Software(w) => { let delegations = w.get_delegations(self.account_index).map_err(ControllerError::WalletError)?; let tasks: FuturesUnordered<_> = delegations @@ -391,7 +331,7 @@ where tasks.try_collect::>().await?.into_iter().flatten().collect() } #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => { + RuntimeWallet::Trezor(w) => { let delegations = w.get_delegations(self.account_index).map_err(ControllerError::WalletError)?; let tasks: FuturesUnordered<_> = delegations @@ -415,26 +355,24 @@ where } pub fn get_created_blocks(&self) -> Result, ControllerError> { - match &self.wallet { - WalletType2::Software(w) => w.get_created_blocks(self.account_index), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_created_blocks(self.account_index), - } - .map_err(ControllerError::WalletError) - .map(|blocks| { - blocks - .into_iter() - .map(|(height, id, pool_id)| { - let pool_id = Address::new(self.chain_config, pool_id).expect("addressable"); - - CreatedBlockInfo { - height, - id, - pool_id: pool_id.to_string(), - } - }) - .collect() - }) + self.wallet + .get_created_blocks(self.account_index) + .map_err(ControllerError::WalletError) + .map(|blocks| { + blocks + .into_iter() + .map(|(height, id, pool_id)| { + let pool_id = + Address::new(self.chain_config, pool_id).expect("addressable"); + + CreatedBlockInfo { + height, + id, + pool_id: pool_id.to_string(), + } + }) + .collect() + }) } async fn get_delegation_share( diff --git a/wallet/wallet-controller/src/runtime_wallet.rs b/wallet/wallet-controller/src/runtime_wallet.rs new file mode 100644 index 0000000000..fa0bed8d24 --- /dev/null +++ b/wallet/wallet-controller/src/runtime_wallet.rs @@ -0,0 +1,1094 @@ +// Copyright (c) 2023 RBB S.r.l +// opensource@mintlayer.org +// SPDX-License-Identifier: MIT +// Licensed under the MIT License; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::{BTreeMap, BTreeSet}; + +use common::address::pubkeyhash::PublicKeyHash; +use common::address::Address; +use common::chain::classic_multisig::ClassicMultisigChallenge; +use common::chain::htlc::HashedTimelockContract; +use common::chain::output_value::OutputValue; +use common::chain::signature::inputsig::arbitrary_message::ArbitraryMessageSignature; +use common::chain::tokens::{ + IsTokenUnfreezable, Metadata, RPCFungibleTokenInfo, TokenId, TokenIssuance, +}; +use common::chain::{ + DelegationId, Destination, GenBlock, PoolId, SignedTransaction, Transaction, TxOutput, + UtxoOutPoint, +}; +use common::primitives::id::WithId; +use common::primitives::{Amount, BlockHeight, Id, H256}; +use crypto::key::hdkd::child_number::ChildNumber; +use crypto::key::hdkd::u31::U31; +use crypto::key::{PrivateKey, PublicKey}; +use crypto::vrf::VRFPublicKey; +use mempool::FeeRate; +use wallet::account::currency_grouper::Currency; +use wallet::account::transaction_list::TransactionList; +use wallet::account::{CoinSelectionAlgo, DelegationData, PoolData, TxInfo, UnconfirmedTokenInfo}; +use wallet::send_request::{PoolOrTokenId, SelectedInputs, StakePoolDataArguments}; +use wallet::signer::software_signer::SoftwareSignerProvider; + +use wallet::wallet::WalletPoolsFilter; +use wallet::wallet_events::WalletEvents; +use wallet::{Wallet, WalletError, WalletResult}; +use wallet_types::account_info::{StandaloneAddressDetails, StandaloneAddresses}; +use wallet_types::partially_signed_transaction::{PartiallySignedTransaction, UtxoAdditionalInfo}; +use wallet_types::seed_phrase::SerializableSeedPhrase; +use wallet_types::signature_status::SignatureStatus; +use wallet_types::utxo_types::{UtxoStates, UtxoTypes}; +use wallet_types::wallet_tx::TxData; +use wallet_types::with_locked::WithLocked; +use wallet_types::KeychainUsageState; + +#[cfg(feature = "trezor")] +use wallet::signer::trezor_signer::TrezorSignerProvider; + +pub enum RuntimeWallet { + Software(Wallet), + #[cfg(feature = "trezor")] + Trezor(Wallet), +} + +impl RuntimeWallet { + pub fn seed_phrase(&self) -> Result, WalletError> { + match self { + RuntimeWallet::Software(w) => w.seed_phrase(), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.seed_phrase(), + } + } + + pub fn delete_seed_phrase(&self) -> Result, WalletError> { + match self { + RuntimeWallet::Software(w) => w.delete_seed_phrase(), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.delete_seed_phrase(), + } + } + + pub fn reset_wallet_to_genesis(&mut self) -> Result<(), WalletError> { + match self { + RuntimeWallet::Software(w) => w.reset_wallet_to_genesis(), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.reset_wallet_to_genesis(), + } + } + + pub fn encrypt_wallet(&mut self, password: &Option) -> Result<(), WalletError> { + match self { + RuntimeWallet::Software(w) => w.encrypt_wallet(password), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.encrypt_wallet(password), + } + } + + pub fn unlock_wallet(&mut self, password: &String) -> Result<(), WalletError> { + match self { + RuntimeWallet::Software(w) => w.unlock_wallet(password), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.unlock_wallet(password), + } + } + + pub fn lock_wallet(&mut self) -> Result<(), WalletError> { + match self { + RuntimeWallet::Software(w) => w.lock_wallet(), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.lock_wallet(), + } + } + + pub fn set_lookahead_size( + &mut self, + lookahead_size: u32, + force_reduce: bool, + ) -> Result<(), WalletError> { + match self { + RuntimeWallet::Software(w) => w.set_lookahead_size(lookahead_size, force_reduce), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.set_lookahead_size(lookahead_size, force_reduce), + } + } + + pub fn wallet_info(&self) -> (H256, Vec>) { + match self { + RuntimeWallet::Software(w) => w.wallet_info(), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.wallet_info(), + } + } + + pub fn create_next_account( + &mut self, + name: Option, + ) -> Result<(U31, Option), WalletError> { + match self { + RuntimeWallet::Software(w) => w.create_next_account(name), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.create_next_account(name), + } + } + + pub fn set_account_name( + &mut self, + account_index: U31, + name: Option, + ) -> Result<(U31, Option), WalletError> { + match self { + RuntimeWallet::Software(w) => w.set_account_name(account_index, name), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.set_account_name(account_index, name), + } + } + + pub fn get_pos_gen_block_data( + &self, + account_index: U31, + pool_id: PoolId, + ) -> Result { + match self { + RuntimeWallet::Software(w) => w.get_pos_gen_block_data(account_index, pool_id), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(_) => Err(WalletError::UnsupportedHardwareWalletOperation), + } + } + + pub fn get_pos_gen_block_data_by_pool_id( + &self, + pool_id: PoolId, + ) -> Result { + match self { + RuntimeWallet::Software(w) => w.get_pos_gen_block_data_by_pool_id(pool_id), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(_) => Err(WalletError::UnsupportedHardwareWalletOperation), + } + } + + pub fn get_pool_ids( + &self, + account_index: U31, + filter: WalletPoolsFilter, + ) -> WalletResult> { + match self { + RuntimeWallet::Software(w) => w.get_pool_ids(account_index, filter), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.get_pool_ids(account_index, filter), + } + } + + pub fn get_best_block(&self) -> BTreeMap, BlockHeight)> { + match self { + RuntimeWallet::Software(w) => w.get_best_block(), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.get_best_block(), + } + } + + pub fn is_locked(&self) -> bool { + match self { + RuntimeWallet::Software(w) => w.is_locked(), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.is_locked(), + } + } + + pub fn get_utxos( + &self, + account_index: U31, + utxo_types: UtxoTypes, + utxo_states: UtxoStates, + with_locked: WithLocked, + ) -> Result, WalletError> { + match self { + RuntimeWallet::Software(w) => { + w.get_utxos(account_index, utxo_types, utxo_states, with_locked) + } + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => { + w.get_utxos(account_index, utxo_types, utxo_states, with_locked) + } + } + } + + pub fn get_transactions_to_be_broadcast( + &mut self, + ) -> Result, WalletError> { + match self { + RuntimeWallet::Software(w) => w.get_transactions_to_be_broadcast(), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.get_transactions_to_be_broadcast(), + } + } + + pub fn get_balance( + &self, + account_index: U31, + utxo_states: UtxoStates, + with_locked: WithLocked, + ) -> WalletResult> { + match self { + RuntimeWallet::Software(w) => w.get_balance(account_index, utxo_states, with_locked), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.get_balance(account_index, utxo_states, with_locked), + } + } + + pub fn get_multisig_utxos( + &self, + account_index: U31, + utxo_types: UtxoTypes, + utxo_states: UtxoStates, + with_locked: WithLocked, + ) -> WalletResult> { + match self { + RuntimeWallet::Software(w) => { + w.get_multisig_utxos(account_index, utxo_types, utxo_states, with_locked) + } + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => { + w.get_multisig_utxos(account_index, utxo_types, utxo_states, with_locked) + } + } + } + + pub fn pending_transactions( + &self, + account_index: U31, + ) -> WalletResult>> { + match self { + RuntimeWallet::Software(w) => w.pending_transactions(account_index), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.pending_transactions(account_index), + } + } + + pub fn mainchain_transactions( + &self, + account_index: U31, + destination: Option, + limit: usize, + ) -> WalletResult> { + match self { + RuntimeWallet::Software(w) => { + w.mainchain_transactions(account_index, destination, limit) + } + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.mainchain_transactions(account_index, destination, limit), + } + } + + pub fn get_transaction_list( + &self, + account_index: U31, + skip: usize, + count: usize, + ) -> WalletResult { + match self { + RuntimeWallet::Software(w) => w.get_transaction_list(account_index, skip, count), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.get_transaction_list(account_index, skip, count), + } + } + + pub fn get_transaction( + &self, + account_index: U31, + transaction_id: Id, + ) -> WalletResult<&TxData> { + match self { + RuntimeWallet::Software(w) => w.get_transaction(account_index, transaction_id), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.get_transaction(account_index, transaction_id), + } + } + + pub fn get_all_issued_addresses( + &self, + account_index: U31, + ) -> WalletResult>> { + match self { + RuntimeWallet::Software(w) => w.get_all_issued_addresses(account_index), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.get_all_issued_addresses(account_index), + } + } + + pub fn get_all_issued_vrf_public_keys( + &self, + account_index: U31, + ) -> WalletResult, bool)>> { + match self { + RuntimeWallet::Software(w) => w.get_all_issued_vrf_public_keys(account_index), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(_) => Err(WalletError::UnsupportedHardwareWalletOperation), + } + } + + pub fn get_legacy_vrf_public_key( + &self, + account_index: U31, + ) -> WalletResult> { + match self { + RuntimeWallet::Software(w) => w.get_legacy_vrf_public_key(account_index), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(_) => Err(WalletError::UnsupportedHardwareWalletOperation), + } + } + + pub fn get_addresses_usage(&self, account_index: U31) -> WalletResult<&KeychainUsageState> { + match self { + RuntimeWallet::Software(w) => w.get_addresses_usage(account_index), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.get_addresses_usage(account_index), + } + } + + pub fn get_all_standalone_addresses( + &self, + account_index: U31, + ) -> WalletResult { + match self { + RuntimeWallet::Software(w) => w.get_all_standalone_addresses(account_index), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.get_all_standalone_addresses(account_index), + } + } + + pub fn get_all_standalone_address_details( + &self, + account_index: U31, + address: Destination, + ) -> WalletResult<( + Destination, + BTreeMap, + StandaloneAddressDetails, + )> { + match self { + RuntimeWallet::Software(w) => { + w.get_all_standalone_address_details(account_index, address) + } + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => { + w.get_all_standalone_address_details(account_index, address) + } + } + } + + pub fn get_created_blocks( + &self, + account_index: U31, + ) -> WalletResult, PoolId)>> { + match self { + RuntimeWallet::Software(w) => w.get_created_blocks(account_index), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.get_created_blocks(account_index), + } + } + + pub fn find_used_tokens( + &self, + account_index: U31, + input_utxos: &[UtxoOutPoint], + ) -> WalletResult> { + match self { + RuntimeWallet::Software(w) => w.find_used_tokens(account_index, input_utxos), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.find_used_tokens(account_index, input_utxos), + } + } + + pub fn get_token_unconfirmed_info( + &self, + account_index: U31, + token_info: RPCFungibleTokenInfo, + ) -> WalletResult { + match self { + RuntimeWallet::Software(w) => w.get_token_unconfirmed_info(account_index, token_info), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.get_token_unconfirmed_info(account_index, token_info), + } + } + + pub fn abandon_transaction( + &mut self, + account_index: U31, + tx_id: Id, + ) -> WalletResult<()> { + match self { + RuntimeWallet::Software(w) => w.abandon_transaction(account_index, tx_id), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.abandon_transaction(account_index, tx_id), + } + } + + pub fn standalone_address_label_rename( + &mut self, + account_index: U31, + address: Destination, + label: Option, + ) -> WalletResult<()> { + match self { + RuntimeWallet::Software(w) => { + w.standalone_address_label_rename(account_index, address, label) + } + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => { + w.standalone_address_label_rename(account_index, address, label) + } + } + } + + pub fn add_standalone_address( + &mut self, + account_index: U31, + address: PublicKeyHash, + label: Option, + ) -> WalletResult<()> { + match self { + RuntimeWallet::Software(w) => w.add_standalone_address(account_index, address, label), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.add_standalone_address(account_index, address, label), + } + } + + pub fn add_standalone_private_key( + &mut self, + account_index: U31, + private_key: PrivateKey, + label: Option, + ) -> WalletResult<()> { + match self { + RuntimeWallet::Software(w) => { + w.add_standalone_private_key(account_index, private_key, label) + } + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => { + w.add_standalone_private_key(account_index, private_key, label) + } + } + } + + pub fn add_standalone_multisig( + &mut self, + account_index: U31, + challenge: ClassicMultisigChallenge, + label: Option, + ) -> WalletResult { + match self { + RuntimeWallet::Software(w) => { + w.add_standalone_multisig(account_index, challenge, label) + } + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.add_standalone_multisig(account_index, challenge, label), + } + } + + pub fn get_new_address( + &mut self, + account_index: U31, + ) -> WalletResult<(ChildNumber, Address)> { + match self { + RuntimeWallet::Software(w) => w.get_new_address(account_index), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.get_new_address(account_index), + } + } + + pub fn find_public_key( + &mut self, + account_index: U31, + address: Destination, + ) -> WalletResult { + match self { + RuntimeWallet::Software(w) => w.find_public_key(account_index, address), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.find_public_key(account_index, address), + } + } + + pub fn get_vrf_key( + &mut self, + account_index: U31, + ) -> WalletResult<(ChildNumber, Address)> { + match self { + RuntimeWallet::Software(w) => w.get_vrf_key(account_index), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(_) => Err(WalletError::UnsupportedHardwareWalletOperation), + } + } + + pub fn issue_new_token( + &mut self, + account_index: U31, + token_issuance: TokenIssuance, + current_fee_rate: FeeRate, + consolidate_fee_rate: FeeRate, + ) -> WalletResult<(TokenId, SignedTransaction)> { + match self { + RuntimeWallet::Software(w) => w.issue_new_token( + account_index, + token_issuance, + current_fee_rate, + consolidate_fee_rate, + ), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.issue_new_token( + account_index, + token_issuance, + current_fee_rate, + consolidate_fee_rate, + ), + } + } + + pub fn issue_new_nft( + &mut self, + account_index: U31, + address: Address, + metadata: Metadata, + current_fee_rate: FeeRate, + consolidate_fee_rate: FeeRate, + ) -> WalletResult<(TokenId, SignedTransaction)> { + match self { + RuntimeWallet::Software(w) => w.issue_new_nft( + account_index, + address, + metadata, + current_fee_rate, + consolidate_fee_rate, + ), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.issue_new_nft( + account_index, + address, + metadata, + current_fee_rate, + consolidate_fee_rate, + ), + } + } + + pub fn mint_tokens( + &mut self, + account_index: U31, + token_info: &UnconfirmedTokenInfo, + amount: Amount, + address: Address, + current_fee_rate: FeeRate, + consolidate_fee_rate: FeeRate, + ) -> Result { + match self { + RuntimeWallet::Software(w) => w.mint_tokens( + account_index, + token_info, + amount, + address, + current_fee_rate, + consolidate_fee_rate, + ), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.mint_tokens( + account_index, + token_info, + amount, + address, + current_fee_rate, + consolidate_fee_rate, + ), + } + } + + pub fn unmint_tokens( + &mut self, + account_index: U31, + token_info: &UnconfirmedTokenInfo, + amount: Amount, + current_fee_rate: FeeRate, + consolidate_fee_rate: FeeRate, + ) -> Result { + match self { + RuntimeWallet::Software(w) => w.unmint_tokens( + account_index, + token_info, + amount, + current_fee_rate, + consolidate_fee_rate, + ), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.unmint_tokens( + account_index, + token_info, + amount, + current_fee_rate, + consolidate_fee_rate, + ), + } + } + + pub fn lock_token_supply( + &mut self, + account_index: U31, + token_info: &UnconfirmedTokenInfo, + current_fee_rate: FeeRate, + consolidate_fee_rate: FeeRate, + ) -> Result { + match self { + RuntimeWallet::Software(w) => w.lock_token_supply( + account_index, + token_info, + current_fee_rate, + consolidate_fee_rate, + ), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.lock_token_supply( + account_index, + token_info, + current_fee_rate, + consolidate_fee_rate, + ), + } + } + + pub fn freeze_token( + &mut self, + account_index: U31, + token_info: &UnconfirmedTokenInfo, + is_token_unfreezable: IsTokenUnfreezable, + current_fee_rate: FeeRate, + consolidate_fee_rate: FeeRate, + ) -> Result { + match self { + RuntimeWallet::Software(w) => w.freeze_token( + account_index, + token_info, + is_token_unfreezable, + current_fee_rate, + consolidate_fee_rate, + ), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.freeze_token( + account_index, + token_info, + is_token_unfreezable, + current_fee_rate, + consolidate_fee_rate, + ), + } + } + + pub fn unfreeze_token( + &mut self, + account_index: U31, + token_info: &UnconfirmedTokenInfo, + current_fee_rate: FeeRate, + consolidate_fee_rate: FeeRate, + ) -> Result { + match self { + RuntimeWallet::Software(w) => w.unfreeze_token( + account_index, + token_info, + current_fee_rate, + consolidate_fee_rate, + ), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.unfreeze_token( + account_index, + token_info, + current_fee_rate, + consolidate_fee_rate, + ), + } + } + + pub fn change_token_authority( + &mut self, + account_index: U31, + token_info: &UnconfirmedTokenInfo, + address: Address, + current_fee_rate: FeeRate, + consolidate_fee_rate: FeeRate, + ) -> Result { + match self { + RuntimeWallet::Software(w) => w.change_token_authority( + account_index, + token_info, + address, + current_fee_rate, + consolidate_fee_rate, + ), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.change_token_authority( + account_index, + token_info, + address, + current_fee_rate, + consolidate_fee_rate, + ), + } + } + + #[allow(clippy::too_many_arguments)] + pub fn create_transaction_to_addresses( + &mut self, + account_index: U31, + outputs: impl IntoIterator, + inputs: SelectedInputs, + change_addresses: BTreeMap>, + current_fee_rate: FeeRate, + consolidate_fee_rate: FeeRate, + additional_utxo_infos: &BTreeMap, + ) -> WalletResult { + match self { + RuntimeWallet::Software(w) => w.create_transaction_to_addresses( + account_index, + outputs, + inputs, + change_addresses, + current_fee_rate, + consolidate_fee_rate, + additional_utxo_infos, + ), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.create_transaction_to_addresses( + account_index, + outputs, + inputs, + change_addresses, + current_fee_rate, + consolidate_fee_rate, + additional_utxo_infos, + ), + } + } + + pub fn create_sweep_transaction( + &mut self, + account_index: U31, + destination_address: Destination, + filtered_inputs: Vec<(UtxoOutPoint, TxOutput)>, + current_fee_rate: FeeRate, + additional_utxo_infos: BTreeMap, + ) -> WalletResult { + match self { + RuntimeWallet::Software(w) => w.create_sweep_transaction( + account_index, + destination_address, + filtered_inputs, + current_fee_rate, + &additional_utxo_infos, + ), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.create_sweep_transaction( + account_index, + destination_address, + filtered_inputs, + current_fee_rate, + &additional_utxo_infos, + ), + } + } + + pub fn get_delegation( + &self, + account_index: U31, + delegation_id: DelegationId, + ) -> WalletResult<&DelegationData> { + match self { + RuntimeWallet::Software(w) => w.get_delegation(account_index, delegation_id), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.get_delegation(account_index, delegation_id), + } + } + + pub fn create_sweep_from_delegation_transaction( + &mut self, + account_index: U31, + destination_address: Address, + delegation_id: DelegationId, + delegation_share: Amount, + current_fee_rate: FeeRate, + ) -> WalletResult { + match self { + RuntimeWallet::Software(w) => w.create_sweep_from_delegation_transaction( + account_index, + destination_address, + delegation_id, + delegation_share, + current_fee_rate, + ), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.create_sweep_from_delegation_transaction( + account_index, + destination_address, + delegation_id, + delegation_share, + current_fee_rate, + ), + } + } + + #[allow(clippy::too_many_arguments)] + pub fn create_unsigned_transaction_to_addresses( + &mut self, + account_index: U31, + outputs: impl IntoIterator, + selected_inputs: SelectedInputs, + selection_algo: Option, + change_addresses: BTreeMap>, + current_fee_rate: FeeRate, + consolidate_fee_rate: FeeRate, + additional_utxo_infos: &BTreeMap, + ) -> WalletResult<(PartiallySignedTransaction, BTreeMap)> { + match self { + RuntimeWallet::Software(w) => w.create_unsigned_transaction_to_addresses( + account_index, + outputs, + selected_inputs, + selection_algo, + change_addresses, + current_fee_rate, + consolidate_fee_rate, + additional_utxo_infos, + ), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.create_unsigned_transaction_to_addresses( + account_index, + outputs, + selected_inputs, + selection_algo, + change_addresses, + current_fee_rate, + consolidate_fee_rate, + additional_utxo_infos, + ), + } + } + + pub fn create_delegation( + &mut self, + account_index: U31, + output: TxOutput, + current_fee_rate: FeeRate, + consolidate_fee_rate: FeeRate, + ) -> WalletResult<(DelegationId, SignedTransaction)> { + match self { + RuntimeWallet::Software(w) => w.create_delegation( + account_index, + vec![output], + current_fee_rate, + consolidate_fee_rate, + ), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.create_delegation( + account_index, + vec![output], + current_fee_rate, + consolidate_fee_rate, + ), + } + } + + pub fn create_transaction_to_addresses_from_delegation( + &mut self, + account_index: U31, + address: Address, + amount: Amount, + delegation_id: DelegationId, + delegation_share: Amount, + current_fee_rate: FeeRate, + ) -> WalletResult { + match self { + RuntimeWallet::Software(w) => w.create_transaction_to_addresses_from_delegation( + account_index, + address, + amount, + delegation_id, + delegation_share, + current_fee_rate, + ), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.create_transaction_to_addresses_from_delegation( + account_index, + address, + amount, + delegation_id, + delegation_share, + current_fee_rate, + ), + } + } + + pub fn create_stake_pool_tx( + &mut self, + account_index: U31, + current_fee_rate: FeeRate, + consolidate_fee_rate: FeeRate, + stake_pool_arguments: StakePoolDataArguments, + ) -> WalletResult { + match self { + RuntimeWallet::Software(w) => w.create_stake_pool_tx( + account_index, + current_fee_rate, + consolidate_fee_rate, + stake_pool_arguments, + ), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(_) => Err(WalletError::UnsupportedHardwareWalletOperation), + } + } + + pub fn decommission_stake_pool( + &mut self, + account_index: U31, + pool_id: PoolId, + staker_balance: Amount, + output_address: Option, + current_fee_rate: FeeRate, + ) -> WalletResult { + match self { + RuntimeWallet::Software(w) => w.decommission_stake_pool( + account_index, + pool_id, + staker_balance, + output_address, + current_fee_rate, + ), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.decommission_stake_pool( + account_index, + pool_id, + staker_balance, + output_address, + current_fee_rate, + ), + } + } + + pub fn decommission_stake_pool_request( + &mut self, + account_index: U31, + pool_id: PoolId, + staker_balance: Amount, + output_address: Option, + current_fee_rate: FeeRate, + ) -> WalletResult { + match self { + RuntimeWallet::Software(w) => w.decommission_stake_pool_request( + account_index, + pool_id, + staker_balance, + output_address, + current_fee_rate, + ), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.decommission_stake_pool_request( + account_index, + pool_id, + staker_balance, + output_address, + current_fee_rate, + ), + } + } + + pub fn create_htlc_tx( + &mut self, + account_index: U31, + output_value: OutputValue, + htlc: HashedTimelockContract, + current_fee_rate: FeeRate, + consolidate_fee_rate: FeeRate, + additional_utxo_infos: &BTreeMap, + ) -> WalletResult { + match self { + RuntimeWallet::Software(w) => w.create_htlc_tx( + account_index, + output_value, + htlc, + current_fee_rate, + consolidate_fee_rate, + additional_utxo_infos, + ), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.create_htlc_tx( + account_index, + output_value, + htlc, + current_fee_rate, + consolidate_fee_rate, + additional_utxo_infos, + ), + } + } + + pub fn sign_raw_transaction( + &mut self, + account_index: U31, + ptx: PartiallySignedTransaction, + ) -> WalletResult<( + PartiallySignedTransaction, + Vec, + Vec, + )> { + match self { + RuntimeWallet::Software(w) => w.sign_raw_transaction(account_index, ptx), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.sign_raw_transaction(account_index, ptx), + } + } + + pub fn sign_challenge( + &mut self, + account_index: U31, + challenge: Vec, + destination: Destination, + ) -> WalletResult { + match self { + RuntimeWallet::Software(w) => w.sign_challenge(account_index, challenge, destination), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.sign_challenge(account_index, challenge, destination), + } + } + + pub fn add_unconfirmed_tx( + &mut self, + tx: SignedTransaction, + wallet_events: &impl WalletEvents, + ) -> WalletResult<()> { + match self { + RuntimeWallet::Software(w) => w.add_unconfirmed_tx(tx, wallet_events), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w.add_unconfirmed_tx(tx, wallet_events), + } + } + + pub fn add_account_unconfirmed_tx( + &mut self, + account_index: U31, + tx: &SignedTransaction, + wallet_events: &impl WalletEvents, + ) -> WalletResult<()> { + match self { + RuntimeWallet::Software(w) => { + w.add_account_unconfirmed_tx(account_index, tx.clone(), wallet_events) + } + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => { + w.add_account_unconfirmed_tx(account_index, tx.clone(), wallet_events) + } + } + } +} diff --git a/wallet/wallet-controller/src/sync/tests/mod.rs b/wallet/wallet-controller/src/sync/tests/mod.rs index 5a717c3b17..50b3c65002 100644 --- a/wallet/wallet-controller/src/sync/tests/mod.rs +++ b/wallet/wallet-controller/src/sync/tests/mod.rs @@ -25,7 +25,7 @@ use chainstate_test_framework::TestFramework; use common::{ chain::{ tokens::{RPCTokenInfo, TokenId}, - DelegationId, PoolId, SignedTransaction, Transaction, + DelegationId, Destination, PoolId, SignedTransaction, Transaction, }, primitives::{time::Time, Amount}, }; @@ -275,6 +275,13 @@ impl NodeInterface for MockNode { unreachable!() } + async fn get_pool_decommission_destination( + &self, + _pool_id: PoolId, + ) -> Result, Self::Error> { + unreachable!() + } + async fn get_delegation_share( &self, _pool_id: PoolId, diff --git a/wallet/wallet-controller/src/synced_controller.rs b/wallet/wallet-controller/src/synced_controller.rs index 3207a34a6c..ca5d5f725b 100644 --- a/wallet/wallet-controller/src/synced_controller.rs +++ b/wallet/wallet-controller/src/synced_controller.rs @@ -21,7 +21,6 @@ use common::{ classic_multisig::ClassicMultisigChallenge, htlc::HashedTimelockContract, output_value::OutputValue, - partially_signed_transaction::PartiallySignedTransaction, signature::inputsig::arbitrary_message::ArbitraryMessageSignature, tokens::{ IsTokenFreezable, IsTokenUnfreezable, Metadata, RPCFungibleTokenInfo, RPCTokenInfo, @@ -50,27 +49,32 @@ use wallet::{ }, destination_getters::{get_tx_output_destination, HtlcSpendingCondition}, send_request::{ - make_address_output, make_address_output_token, make_create_delegation_output, - make_data_deposit_output, SelectedInputs, StakePoolDataArguments, + get_referenced_token_ids, make_address_output, make_address_output_token, + make_create_delegation_output, make_data_deposit_output, PoolOrTokenId, SelectedInputs, + StakePoolDataArguments, }, wallet::WalletPoolsFilter, wallet_events::WalletEvents, WalletError, WalletResult, }; use wallet_types::{ + partially_signed_transaction::{ + PartiallySignedTransaction, TokenAdditionalInfo, UtxoAdditionalInfo, + }, signature_status::SignatureStatus, utxo_types::{UtxoState, UtxoType}, with_locked::WithLocked, }; use crate::{ - into_balances, + helpers::{fetch_token_info, fetch_utxo, into_balances, tx_to_partially_signed_tx}, + runtime_wallet::RuntimeWallet, types::{Balances, GenericCurrencyTransfer}, - ControllerConfig, ControllerError, WalletType2, + ControllerConfig, ControllerError, }; pub struct SyncedController<'a, T, W, B: storage::Backend + 'static> { - wallet: &'a mut WalletType2, + wallet: &'a mut RuntimeWallet, rpc_client: T, chain_config: &'a ChainConfig, wallet_events: &'a W, @@ -86,7 +90,7 @@ where W: WalletEvents, { pub fn new( - wallet: &'a mut WalletType2, + wallet: &'a mut RuntimeWallet, rpc_client: T, chain_config: &'a ChainConfig, wallet_events: &'a W, @@ -105,25 +109,14 @@ where } } - pub async fn get_token_info( - &self, - token_id: TokenId, - ) -> Result> { - self.rpc_client - .get_token_info(token_id) - .await - .map_err(ControllerError::NodeCallError)? - .ok_or(ControllerError::WalletError(WalletError::UnknownTokenId( - token_id, - ))) - } - async fn fetch_token_infos( &self, tokens: BTreeSet, ) -> Result, ControllerError> { - let tasks: FuturesUnordered<_> = - tokens.into_iter().map(|token_id| self.get_token_info(token_id)).collect(); + let tasks: FuturesUnordered<_> = tokens + .into_iter() + .map(|token_id| fetch_token_info(&self.rpc_client, token_id)) + .collect(); tasks.try_collect().await } @@ -132,17 +125,15 @@ where &self, input_utxos: &[UtxoOutPoint], ) -> Result<(), ControllerError> { - let token_ids = match &self.wallet { - WalletType2::Software(w) => w.find_used_tokens(self.account_index, input_utxos), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.find_used_tokens(self.account_index, input_utxos), - } - .map_err(ControllerError::WalletError)?; + let token_ids = self + .wallet + .find_used_tokens(self.account_index, input_utxos) + .map_err(ControllerError::WalletError)?; for token_info in self.fetch_token_infos(token_ids).await? { match token_info { RPCTokenInfo::FungibleToken(token_info) => { - self.check_fungible_token_is_usable(&token_info)? + self.check_fungible_token_is_usable(token_info)? } RPCTokenInfo::NonFungibleToken(_) => {} } @@ -152,18 +143,13 @@ where pub fn check_fungible_token_is_usable( &self, - token_info: &RPCFungibleTokenInfo, + token_info: RPCFungibleTokenInfo, ) -> Result<(), ControllerError> { - match &self.wallet { - WalletType2::Software(w) => { - w.get_token_unconfirmed_info(self.account_index, token_info) - } - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_token_unconfirmed_info(self.account_index, token_info), - } - .map_err(ControllerError::WalletError)? - .check_can_be_used() - .map_err(ControllerError::WalletError)?; + self.wallet + .get_token_unconfirmed_info(self.account_index, token_info) + .map_err(ControllerError::WalletError)? + .check_can_be_used() + .map_err(ControllerError::WalletError)?; Ok(()) } @@ -171,48 +157,67 @@ where /// Filter out utxos that contain tokens that are frozen and can't be used async fn filter_out_utxos_with_frozen_tokens( &self, - input_utxos: Vec<(UtxoOutPoint, TxOutput, Option)>, - ) -> Result)>, ControllerError> { + input_utxos: Vec<(UtxoOutPoint, TxOutput)>, + ) -> Result< + ( + Vec<(UtxoOutPoint, TxOutput)>, + BTreeMap, + ), + ControllerError, + > { let mut result = vec![]; + let mut additional_utxo_infos = BTreeMap::new(); for utxo in input_utxos { - if let Some(token_id) = utxo.2 { - let token_info = self.get_token_info(token_id).await?; + let token_ids = get_referenced_token_ids(&utxo.1); + if token_ids.is_empty() { + result.push(utxo); + } else { + let token_infos = self.fetch_token_infos(token_ids).await?; + let ok_to_use = token_infos.iter().try_fold( + true, + |all_ok, token_info| -> Result> { + let all_ok = all_ok + && match &token_info { + RPCTokenInfo::FungibleToken(token_info) => self + .wallet + .get_token_unconfirmed_info( + self.account_index, + token_info.clone(), + ) + .map_err(ControllerError::WalletError)? + .check_can_be_used() + .is_ok(), + RPCTokenInfo::NonFungibleToken(_) => true, + }; + Ok(all_ok) + }, + )?; - let ok_to_use = match token_info { - RPCTokenInfo::FungibleToken(token_info) => match &self.wallet { - WalletType2::Software(w) => { - w.get_token_unconfirmed_info(self.account_index, &token_info) - } - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => { - w.get_token_unconfirmed_info(self.account_index, &token_info) - } - } - .map_err(ControllerError::WalletError)? - .check_can_be_used() - .is_ok(), - RPCTokenInfo::NonFungibleToken(_) => true, - }; if ok_to_use { result.push(utxo); + for token_info in token_infos { + additional_utxo_infos.insert( + PoolOrTokenId::TokenId(token_info.token_id()), + UtxoAdditionalInfo::TokenInfo(TokenAdditionalInfo { + num_decimals: token_info.token_number_of_decimals(), + ticker: token_info.token_ticker().to_vec(), + }), + ); + } } - } else { - result.push(utxo); } } - Ok(result) + + Ok((result, additional_utxo_infos)) } pub fn abandon_transaction( &mut self, tx_id: Id, ) -> Result<(), ControllerError> { - match &mut self.wallet { - WalletType2::Software(w) => w.abandon_transaction(self.account_index, tx_id), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.abandon_transaction(self.account_index, tx_id), - } - .map_err(ControllerError::WalletError) + self.wallet + .abandon_transaction(self.account_index, tx_id) + .map_err(ControllerError::WalletError) } pub fn standalone_address_label_rename( @@ -220,16 +225,9 @@ where address: Destination, label: Option, ) -> Result<(), ControllerError> { - match &mut self.wallet { - WalletType2::Software(w) => { - w.standalone_address_label_rename(self.account_index, address, label) - } - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => { - w.standalone_address_label_rename(self.account_index, address, label) - } - } - .map_err(ControllerError::WalletError) + self.wallet + .standalone_address_label_rename(self.account_index, address, label) + .map_err(ControllerError::WalletError) } pub fn add_standalone_address( @@ -237,14 +235,9 @@ where address: PublicKeyHash, label: Option, ) -> Result<(), ControllerError> { - match &mut self.wallet { - WalletType2::Software(w) => { - w.add_standalone_address(self.account_index, address, label) - } - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.add_standalone_address(self.account_index, address, label), - } - .map_err(ControllerError::WalletError) + self.wallet + .add_standalone_address(self.account_index, address, label) + .map_err(ControllerError::WalletError) } pub fn add_standalone_private_key( @@ -252,16 +245,9 @@ where private_key: PrivateKey, label: Option, ) -> Result<(), ControllerError> { - match &mut self.wallet { - WalletType2::Software(w) => { - w.add_standalone_private_key(self.account_index, private_key, label) - } - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => { - w.add_standalone_private_key(self.account_index, private_key, label) - } - } - .map_err(ControllerError::WalletError) + self.wallet + .add_standalone_private_key(self.account_index, private_key, label) + .map_err(ControllerError::WalletError) } pub fn add_standalone_multisig( @@ -269,51 +255,34 @@ where challenge: ClassicMultisigChallenge, label: Option, ) -> Result> { - match &mut self.wallet { - WalletType2::Software(w) => { - w.add_standalone_multisig(self.account_index, challenge, label) - } - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => { - w.add_standalone_multisig(self.account_index, challenge, label) - } - } - .map_err(ControllerError::WalletError) + self.wallet + .add_standalone_multisig(self.account_index, challenge, label) + .map_err(ControllerError::WalletError) } pub fn new_address( &mut self, ) -> Result<(ChildNumber, Address), ControllerError> { - match &mut self.wallet { - WalletType2::Software(w) => w.get_new_address(self.account_index), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_new_address(self.account_index), - } - .map_err(ControllerError::WalletError) + self.wallet + .get_new_address(self.account_index) + .map_err(ControllerError::WalletError) } pub fn find_public_key( &mut self, address: Destination, ) -> Result> { - match &mut self.wallet { - WalletType2::Software(w) => w.find_public_key(self.account_index, address), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.find_public_key(self.account_index, address), - } - .map_err(ControllerError::WalletError) + self.wallet + .find_public_key(self.account_index, address) + .map_err(ControllerError::WalletError) } pub fn new_vrf_key( &mut self, ) -> Result<(ChildNumber, Address), ControllerError> { - match &mut self.wallet { - WalletType2::Software(w) => { - w.get_vrf_key(self.account_index).map_err(ControllerError::WalletError) - } - #[cfg(feature = "trezor")] - WalletType2::Trezor(_) => Err(ControllerError::UnsupportedHardwareWalletOperation), - } + self.wallet + .get_vrf_key(self.account_index) + .map_err(ControllerError::WalletError) } pub async fn issue_new_token( @@ -328,37 +297,21 @@ where self.create_and_send_tx_with_id( move |current_fee_rate: FeeRate, consolidate_fee_rate: FeeRate, - wallet: &mut WalletType2, + wallet: &mut RuntimeWallet, account_index: U31| { - match wallet { - WalletType2::Software(w) => w.issue_new_token( - account_index, - TokenIssuance::V1(TokenIssuanceV1 { - token_ticker, - number_of_decimals, - metadata_uri, - total_supply: token_total_supply, - authority: address.into_object(), - is_freezable, - }), - current_fee_rate, - consolidate_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.issue_new_token( - account_index, - TokenIssuance::V1(TokenIssuanceV1 { - token_ticker, - number_of_decimals, - metadata_uri, - total_supply: token_total_supply, - authority: address.into_object(), - is_freezable, - }), - current_fee_rate, - consolidate_fee_rate, - ), - } + wallet.issue_new_token( + account_index, + TokenIssuance::V1(TokenIssuanceV1 { + token_ticker, + number_of_decimals, + metadata_uri, + total_supply: token_total_supply, + authority: address.into_object(), + is_freezable, + }), + current_fee_rate, + consolidate_fee_rate, + ) }, ) .await @@ -372,25 +325,15 @@ where self.create_and_send_tx_with_id( move |current_fee_rate: FeeRate, consolidate_fee_rate: FeeRate, - wallet: &mut WalletType2, + wallet: &mut RuntimeWallet, account_index: U31| { - match wallet { - WalletType2::Software(w) => w.issue_new_nft( - account_index, - address, - metadata, - current_fee_rate, - consolidate_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.issue_new_nft( - account_index, - address, - metadata, - current_fee_rate, - consolidate_fee_rate, - ), - } + wallet.issue_new_nft( + account_index, + address, + metadata, + current_fee_rate, + consolidate_fee_rate, + ) }, ) .await @@ -403,32 +346,21 @@ where address: Address, ) -> Result> { self.create_and_send_token_tx( - &token_info, + token_info, move |current_fee_rate: FeeRate, consolidate_fee_rate: FeeRate, - wallet: &mut WalletType2, + wallet: &mut RuntimeWallet, account_index: U31, token_info: &UnconfirmedTokenInfo| { token_info.check_can_be_used()?; - match wallet { - WalletType2::Software(w) => w.mint_tokens( - account_index, - token_info, - amount, - address, - current_fee_rate, - consolidate_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.mint_tokens( - account_index, - token_info, - amount, - address, - current_fee_rate, - consolidate_fee_rate, - ), - } + wallet.mint_tokens( + account_index, + token_info, + amount, + address, + current_fee_rate, + consolidate_fee_rate, + ) }, ) .await @@ -439,30 +371,20 @@ where amount: Amount, ) -> Result> { self.create_and_send_token_tx( - &token_info, + token_info, move |current_fee_rate: FeeRate, consolidate_fee_rate: FeeRate, - wallet: &mut WalletType2, + wallet: &mut RuntimeWallet, account_index: U31, token_info: &UnconfirmedTokenInfo| { token_info.check_can_be_used()?; - match wallet { - WalletType2::Software(w) => w.unmint_tokens( - account_index, - token_info, - amount, - current_fee_rate, - consolidate_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.unmint_tokens( - account_index, - token_info, - amount, - current_fee_rate, - consolidate_fee_rate, - ), - } + wallet.unmint_tokens( + account_index, + token_info, + amount, + current_fee_rate, + consolidate_fee_rate, + ) }, ) .await @@ -473,28 +395,19 @@ where token_info: RPCTokenInfo, ) -> Result> { self.create_and_send_token_tx( - &token_info, + token_info, move |current_fee_rate: FeeRate, consolidate_fee_rate: FeeRate, - wallet: &mut WalletType2, + wallet: &mut RuntimeWallet, account_index: U31, token_info: &UnconfirmedTokenInfo| { token_info.check_can_be_used()?; - match wallet { - WalletType2::Software(w) => w.lock_token_supply( - account_index, - token_info, - current_fee_rate, - consolidate_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.lock_token_supply( - account_index, - token_info, - current_fee_rate, - consolidate_fee_rate, - ), - } + wallet.lock_token_supply( + account_index, + token_info, + current_fee_rate, + consolidate_fee_rate, + ) }, ) .await @@ -508,29 +421,19 @@ where is_token_unfreezable: IsTokenUnfreezable, ) -> Result> { self.create_and_send_token_tx( - &token_info, + token_info, move |current_fee_rate: FeeRate, consolidate_fee_rate: FeeRate, - wallet: &mut WalletType2, + wallet: &mut RuntimeWallet, account_index: U31, token_info: &UnconfirmedTokenInfo| { - match wallet { - WalletType2::Software(w) => w.freeze_token( - account_index, - token_info, - is_token_unfreezable, - current_fee_rate, - consolidate_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.freeze_token( - account_index, - token_info, - is_token_unfreezable, - current_fee_rate, - consolidate_fee_rate, - ), - } + wallet.freeze_token( + account_index, + token_info, + is_token_unfreezable, + current_fee_rate, + consolidate_fee_rate, + ) }, ) .await @@ -542,27 +445,18 @@ where token_info: RPCTokenInfo, ) -> Result> { self.create_and_send_token_tx( - &token_info, + token_info, move |current_fee_rate: FeeRate, consolidate_fee_rate: FeeRate, - wallet: &mut WalletType2, + wallet: &mut RuntimeWallet, account_index: U31, token_info: &UnconfirmedTokenInfo| { - match wallet { - WalletType2::Software(w) => w.unfreeze_token( - account_index, - token_info, - current_fee_rate, - consolidate_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.unfreeze_token( - account_index, - token_info, - current_fee_rate, - consolidate_fee_rate, - ), - } + wallet.unfreeze_token( + account_index, + token_info, + current_fee_rate, + consolidate_fee_rate, + ) }, ) .await @@ -576,29 +470,19 @@ where address: Address, ) -> Result> { self.create_and_send_token_tx( - &token_info, + token_info, move |current_fee_rate: FeeRate, consolidate_fee_rate: FeeRate, - wallet: &mut WalletType2, + wallet: &mut RuntimeWallet, account_index: U31, token_info: &UnconfirmedTokenInfo| { - match wallet { - WalletType2::Software(w) => w.change_token_authority( - account_index, - token_info, - address, - current_fee_rate, - consolidate_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.change_token_authority( - account_index, - token_info, - address, - current_fee_rate, - consolidate_fee_rate, - ), - } + wallet.change_token_authority( + account_index, + token_info, + address, + current_fee_rate, + consolidate_fee_rate, + ) }, ) .await @@ -614,27 +498,17 @@ where self.create_and_send_tx( move |current_fee_rate: FeeRate, consolidate_fee_rate: FeeRate, - wallet: &mut WalletType2, + wallet: &mut RuntimeWallet, account_index: U31| { - match wallet { - WalletType2::Software(w) => w.create_transaction_to_addresses( - account_index, - outputs, - SelectedInputs::Utxos(vec![]), - BTreeMap::new(), - current_fee_rate, - consolidate_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.create_transaction_to_addresses( - account_index, - outputs, - SelectedInputs::Utxos(vec![]), - BTreeMap::new(), - current_fee_rate, - consolidate_fee_rate, - ), - } + wallet.create_transaction_to_addresses( + account_index, + outputs, + SelectedInputs::Utxos(vec![]), + BTreeMap::new(), + current_fee_rate, + consolidate_fee_rate, + &BTreeMap::new(), + ) }, ) .await @@ -656,27 +530,17 @@ where self.create_and_send_tx( move |current_fee_rate: FeeRate, consolidate_fee_rate: FeeRate, - wallet: &mut WalletType2, + wallet: &mut RuntimeWallet, account_index: U31| { - match wallet { - WalletType2::Software(w) => w.create_transaction_to_addresses( - account_index, - [output], - SelectedInputs::Utxos(selected_utxos), - BTreeMap::new(), - current_fee_rate, - consolidate_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.create_transaction_to_addresses( - account_index, - [output], - SelectedInputs::Utxos(selected_utxos), - BTreeMap::new(), - current_fee_rate, - consolidate_fee_rate, - ), - } + wallet.create_transaction_to_addresses( + account_index, + [output], + SelectedInputs::Utxos(selected_utxos), + BTreeMap::new(), + current_fee_rate, + consolidate_fee_rate, + &BTreeMap::new(), + ) }, ) .await @@ -689,28 +553,19 @@ where destination_address: Destination, from_addresses: BTreeSet, ) -> Result> { - let selected_utxos = match &self.wallet { - WalletType2::Software(w) => w.get_utxos( - self.account_index, - UtxoType::Transfer | UtxoType::LockThenTransfer | UtxoType::IssueNft, - UtxoState::Confirmed | UtxoState::Inactive, - WithLocked::Unlocked, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_utxos( - self.account_index, - UtxoType::Transfer | UtxoType::LockThenTransfer | UtxoType::IssueNft, - UtxoState::Confirmed | UtxoState::Inactive, - WithLocked::Unlocked, - ), - } - .map_err(ControllerError::WalletError)?; + let selected_utxos = self.wallet.get_utxos( + self.account_index, + UtxoType::Transfer | UtxoType::LockThenTransfer | UtxoType::IssueNft, + UtxoState::Confirmed | UtxoState::Inactive, + WithLocked::Unlocked, + )?; + + let (inputs, additional_utxo_infos) = + self.filter_out_utxos_with_frozen_tokens(selected_utxos).await?; - let filtered_inputs = self - .filter_out_utxos_with_frozen_tokens(selected_utxos) - .await? + let filtered_inputs = inputs .into_iter() - .filter(|(_, output, _)| { + .filter(|(_, output)| { get_tx_output_destination(output, &|_| None, HtlcSpendingCondition::Skip) .map_or(false, |dest| from_addresses.contains(&dest)) }) @@ -719,23 +574,15 @@ where self.create_and_send_tx( move |current_fee_rate: FeeRate, _consolidate_fee_rate: FeeRate, - wallet: &mut WalletType2, + wallet: &mut RuntimeWallet, account_index: U31| { - match wallet { - WalletType2::Software(w) => w.create_sweep_transaction( - account_index, - destination_address, - filtered_inputs, - current_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.create_sweep_transaction( - account_index, - destination_address, - filtered_inputs, - current_fee_rate, - ), - } + wallet.create_sweep_transaction( + account_index, + destination_address, + filtered_inputs, + current_fee_rate, + additional_utxo_infos, + ) }, ) .await @@ -748,13 +595,11 @@ where destination_address: Address, delegation_id: DelegationId, ) -> Result> { - let pool_id = match &self.wallet { - WalletType2::Software(w) => w.get_delegation(self.account_index, delegation_id), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_delegation(self.account_index, delegation_id), - } - .map_err(ControllerError::WalletError)? - .pool_id; + let pool_id = self + .wallet + .get_delegation(self.account_index, delegation_id) + .map_err(ControllerError::WalletError)? + .pool_id; let delegation_share = self .rpc_client @@ -768,25 +613,15 @@ where self.create_and_send_tx( move |current_fee_rate: FeeRate, _consolidate_fee_rate: FeeRate, - wallet: &mut WalletType2, + wallet: &mut RuntimeWallet, account_index: U31| { - match wallet { - WalletType2::Software(w) => w.create_sweep_from_delegation_transaction( - account_index, - destination_address, - delegation_id, - delegation_share, - current_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.create_sweep_from_delegation_transaction( - account_index, - destination_address, - delegation_id, - delegation_share, - current_fee_rate, - ), - } + wallet.create_sweep_from_delegation_transaction( + account_index, + destination_address, + delegation_id, + delegation_share, + current_fee_rate, + ) }, ) .await @@ -806,7 +641,7 @@ where ) -> Result<(PartiallySignedTransaction, Balances), ControllerError> { let output = make_address_output(address, amount); - let utxo_output = self.fetch_utxo(&selected_utxo).await?; + let utxo_output = fetch_utxo(&self.rpc_client, &selected_utxo, self.wallet).await?; let change_address = if let Some(change_address) = change_address { change_address } else { @@ -825,18 +660,9 @@ where let (current_fee_rate, consolidate_fee_rate) = self.get_current_and_consolidation_fee_rate().await?; - let (req, fees) = match &mut self.wallet { - WalletType2::Software(w) => w.create_unsigned_transaction_to_addresses( - self.account_index, - [output], - selected_inputs, - None, - [(Currency::Coin, change_address)].into(), - current_fee_rate, - consolidate_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.create_unsigned_transaction_to_addresses( + let (req, fees) = self + .wallet + .create_unsigned_transaction_to_addresses( self.account_index, [output], selected_inputs, @@ -844,9 +670,9 @@ where [(Currency::Coin, change_address)].into(), current_fee_rate, consolidate_fee_rate, - ), - } - .map_err(ControllerError::WalletError)?; + &BTreeMap::new(), + ) + .map_err(ControllerError::WalletError)?; let fees = into_balances(&self.rpc_client, self.chain_config, fees).await?; @@ -888,15 +714,23 @@ where ControllerError::::ExpectingNonEmptyOutputs ); - let outputs = { + let (outputs, additional_utxo_infos) = { let mut result = Vec::new(); + let mut additional_utxo_infos = BTreeMap::new(); for (token_id, outputs_vec) in outputs { - let token_info = self.get_token_info(token_id).await?; + let token_info = fetch_token_info(&self.rpc_client, token_id).await?; + additional_utxo_infos.insert( + PoolOrTokenId::TokenId(token_id), + UtxoAdditionalInfo::TokenInfo(TokenAdditionalInfo { + num_decimals: token_info.token_number_of_decimals(), + ticker: token_info.token_ticker().to_vec(), + }), + ); match &token_info { RPCTokenInfo::FungibleToken(token_info) => { - self.check_fungible_token_is_usable(token_info)? + self.check_fungible_token_is_usable(token_info.clone())? } RPCTokenInfo::NonFungibleToken(_) => { return Err(ControllerError::::NotFungibleToken(token_id)); @@ -910,33 +744,23 @@ where .map_err(ControllerError::InvalidTxOutput)?; } - result + (result, additional_utxo_infos) }; let (inputs, change_addresses) = { let mut inputs = inputs; let mut change_addresses = change_addresses; - let all_utxos = match &self.wallet { - WalletType2::Software(w) => w.get_utxos( - self.account_index, - UtxoType::Transfer | UtxoType::LockThenTransfer, - UtxoState::Confirmed | UtxoState::InMempool | UtxoState::Inactive, - WithLocked::Unlocked, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_utxos( - self.account_index, - UtxoType::Transfer | UtxoType::LockThenTransfer, - UtxoState::Confirmed | UtxoState::InMempool | UtxoState::Inactive, - WithLocked::Unlocked, - ), - } - .map_err(ControllerError::WalletError)?; + let all_utxos = self.wallet.get_utxos( + self.account_index, + UtxoType::Transfer | UtxoType::LockThenTransfer, + UtxoState::Confirmed | UtxoState::InMempool | UtxoState::Inactive, + WithLocked::Unlocked, + )?; let all_coin_utxos = all_utxos .into_iter() - .filter_map(|(o, txo, _)| { + .filter_map(|(o, txo)| { let (val, dest) = match &txo { TxOutput::Transfer(val, dest) | TxOutput::LockThenTransfer(val, dest, _) => (val, dest), @@ -990,28 +814,16 @@ where let (current_fee_rate, consolidate_fee_rate) = self.get_current_and_consolidation_fee_rate().await?; - let (tx, fees) = match &mut self.wallet { - WalletType2::Software(w) => w.create_unsigned_transaction_to_addresses( - self.account_index, - outputs, - selected_inputs, - Some(CoinSelectionAlgo::Randomize), - change_addresses, - current_fee_rate, - consolidate_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.create_unsigned_transaction_to_addresses( - self.account_index, - outputs, - selected_inputs, - Some(CoinSelectionAlgo::Randomize), - change_addresses, - current_fee_rate, - consolidate_fee_rate, - ), - } - .map_err(ControllerError::WalletError)?; + let (tx, fees) = self.wallet.create_unsigned_transaction_to_addresses( + self.account_index, + outputs, + selected_inputs, + Some(CoinSelectionAlgo::Randomize), + change_addresses, + current_fee_rate, + consolidate_fee_rate, + &additional_utxo_infos, + )?; let fees = into_balances(&self.rpc_client, self.chain_config, fees).await?; @@ -1030,23 +842,14 @@ where self.create_and_send_tx_with_id( move |current_fee_rate: FeeRate, consolidate_fee_rate: FeeRate, - wallet: &mut WalletType2, + wallet: &mut RuntimeWallet, account_index: U31| { - match wallet { - WalletType2::Software(w) => w.create_delegation( - account_index, - vec![output], - current_fee_rate, - consolidate_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.create_delegation( - account_index, - vec![output], - current_fee_rate, - consolidate_fee_rate, - ), - } + wallet.create_delegation( + account_index, + output, + current_fee_rate, + consolidate_fee_rate, + ) }, ) .await @@ -1063,27 +866,17 @@ where self.create_and_send_tx( move |current_fee_rate: FeeRate, consolidate_fee_rate: FeeRate, - wallet: &mut WalletType2, + wallet: &mut RuntimeWallet, account_index: U31| { - match wallet { - WalletType2::Software(w) => w.create_transaction_to_addresses( - account_index, - [output], - SelectedInputs::Utxos(vec![]), - BTreeMap::new(), - current_fee_rate, - consolidate_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.create_transaction_to_addresses( - account_index, - [output], - SelectedInputs::Utxos(vec![]), - BTreeMap::new(), - current_fee_rate, - consolidate_fee_rate, - ), - } + wallet.create_transaction_to_addresses( + account_index, + [output], + SelectedInputs::Utxos(vec![]), + BTreeMap::new(), + current_fee_rate, + consolidate_fee_rate, + &BTreeMap::new(), + ) }, ) .await @@ -1097,13 +890,7 @@ where amount: Amount, delegation_id: DelegationId, ) -> Result> { - let pool_id = match &self.wallet { - WalletType2::Software(w) => w.get_delegation(self.account_index, delegation_id), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_delegation(self.account_index, delegation_id), - } - .map_err(ControllerError::WalletError)? - .pool_id; + let pool_id = self.wallet.get_delegation(self.account_index, delegation_id)?.pool_id; let delegation_share = self .rpc_client @@ -1117,27 +904,16 @@ where self.create_and_send_tx( move |current_fee_rate: FeeRate, _consolidate_fee_rate: FeeRate, - wallet: &mut WalletType2, + wallet: &mut RuntimeWallet, account_index: U31| { - match wallet { - WalletType2::Software(w) => w.create_transaction_to_addresses_from_delegation( - account_index, - address, - amount, - delegation_id, - delegation_share, - current_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.create_transaction_to_addresses_from_delegation( - account_index, - address, - amount, - delegation_id, - delegation_share, - current_fee_rate, - ), - } + wallet.create_transaction_to_addresses_from_delegation( + account_index, + address, + amount, + delegation_id, + delegation_share, + current_fee_rate, + ) }, ) .await @@ -1153,32 +929,29 @@ where ) -> Result> { let output = make_address_output_token(address, amount, token_info.token_id()); self.create_and_send_token_tx( - &token_info, + token_info, move |current_fee_rate: FeeRate, consolidate_fee_rate: FeeRate, - wallet: &mut WalletType2, + wallet: &mut RuntimeWallet, account_index: U31, token_info: &UnconfirmedTokenInfo| { token_info.check_can_be_used()?; - match wallet { - WalletType2::Software(w) => w.create_transaction_to_addresses( - account_index, - [output], - SelectedInputs::Utxos(vec![]), - BTreeMap::new(), - current_fee_rate, - consolidate_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.create_transaction_to_addresses( - account_index, - [output], - SelectedInputs::Utxos(vec![]), - BTreeMap::new(), - current_fee_rate, - consolidate_fee_rate, - ), - } + let additional_info = BTreeMap::from_iter([( + PoolOrTokenId::TokenId(token_info.token_id()), + UtxoAdditionalInfo::TokenInfo(TokenAdditionalInfo { + num_decimals: token_info.num_decimals(), + ticker: token_info.token_ticker().to_vec(), + }), + )]); + wallet.create_transaction_to_addresses( + account_index, + [output], + SelectedInputs::Utxos(vec![]), + BTreeMap::new(), + current_fee_rate, + consolidate_fee_rate, + &additional_info, + ) }, ) .await @@ -1195,27 +968,19 @@ where self.create_and_send_tx( move |current_fee_rate: FeeRate, consolidate_fee_rate: FeeRate, - wallet: &mut WalletType2, + wallet: &mut RuntimeWallet, account_index: U31| { - match wallet { - WalletType2::Software(w) => w - .create_stake_pool_tx( - account_index, - current_fee_rate, - consolidate_fee_rate, - StakePoolDataArguments { - amount, - margin_ratio_per_thousand, - cost_per_block, - decommission_key, - }, - ) - .map_err(ControllerError::WalletError), - #[cfg(feature = "trezor")] - WalletType2::Trezor(_) => { - Err(ControllerError::UnsupportedHardwareWalletOperation) - } - } + wallet.create_stake_pool_tx( + account_index, + current_fee_rate, + consolidate_fee_rate, + StakePoolDataArguments { + amount, + margin_ratio_per_thousand, + cost_per_block, + decommission_key, + }, + ) }, ) .await @@ -1239,25 +1004,15 @@ where self.create_and_send_tx( move |current_fee_rate: FeeRate, _consolidate_fee_rate: FeeRate, - wallet: &mut WalletType2, + wallet: &mut RuntimeWallet, account_index: U31| { - match wallet { - WalletType2::Software(w) => w.decommission_stake_pool( - account_index, - pool_id, - staker_balance, - output_address, - current_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.decommission_stake_pool( - account_index, - pool_id, - staker_balance, - output_address, - current_fee_rate, - ), - } + wallet.decommission_stake_pool( + account_index, + pool_id, + staker_balance, + output_address, + current_fee_rate, + ) }, ) .await @@ -1280,73 +1035,42 @@ where let (current_fee_rate, _) = self.get_current_and_consolidation_fee_rate().await?; - match &mut self.wallet { - WalletType2::Software(w) => w.decommission_stake_pool_request( - self.account_index, - pool_id, - staker_balance, - output_address, - current_fee_rate, - ), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.decommission_stake_pool_request( + self.wallet + .decommission_stake_pool_request( self.account_index, pool_id, staker_balance, output_address, current_fee_rate, - ), - } - .map_err(ControllerError::WalletError) + ) + .map_err(ControllerError::WalletError) } pub async fn create_htlc_tx( &mut self, output_value: OutputValue, htlc: HashedTimelockContract, + additional_utxo_infos: &BTreeMap, ) -> Result> { let (current_fee_rate, consolidate_fee_rate) = self.get_current_and_consolidation_fee_rate().await?; - let result = match self.wallet { - WalletType2::Software(w) => w.create_htlc_tx( - self.account_index, - output_value, - htlc, - current_fee_rate, - consolidate_fee_rate, - )?, - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.create_htlc_tx( - self.account_index, - output_value, - htlc, - current_fee_rate, - consolidate_fee_rate, - )?, - }; + let result = self.wallet.create_htlc_tx( + self.account_index, + output_value, + htlc, + current_fee_rate, + consolidate_fee_rate, + additional_utxo_infos, + )?; Ok(result) } /// Checks if the wallet has stake pools and marks this account for staking. pub fn start_staking(&mut self) -> Result<(), ControllerError> { - utils::ensure!( - !match &self.wallet { - WalletType2::Software(w) => w.is_locked(), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.is_locked(), - }, - ControllerError::WalletIsLocked - ); + utils::ensure!(!self.wallet.is_locked(), ControllerError::WalletIsLocked); // Make sure that account_index is valid and that pools exist - let pool_ids = match &mut self.wallet { - WalletType2::Software(w) => { - w.get_pool_ids(self.account_index, WalletPoolsFilter::Stake) - } - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.get_pool_ids(self.account_index, WalletPoolsFilter::Stake), - } - .map_err(ControllerError::WalletError)?; + let pool_ids = self.wallet.get_pool_ids(self.account_index, WalletPoolsFilter::Stake)?; utils::ensure!(!pool_ids.is_empty(), ControllerError::NoStakingPool); log::info!("Start staking, account_index: {}", self.account_index); self.staking_started.insert(self.account_index); @@ -1355,7 +1079,7 @@ where /// Tries to sign any unsigned inputs of a raw or partially signed transaction with the private /// keys in this wallet. - pub fn sign_raw_transaction( + pub async fn sign_raw_transaction( &mut self, tx: TransactionToSign, ) -> Result< @@ -1366,12 +1090,16 @@ where ), ControllerError, > { - match &mut self.wallet { - WalletType2::Software(w) => w.sign_raw_transaction(self.account_index, tx), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.sign_raw_transaction(self.account_index, tx), - } - .map_err(ControllerError::WalletError) + let ptx = match tx { + TransactionToSign::Partial(ptx) => ptx, + TransactionToSign::Tx(tx) => { + tx_to_partially_signed_tx(&self.rpc_client, self.wallet, tx).await? + } + }; + + self.wallet + .sign_raw_transaction(self.account_index, ptx) + .map_err(ControllerError::WalletError) } pub fn sign_challenge( @@ -1379,23 +1107,15 @@ where challenge: Vec, destination: Destination, ) -> Result> { - match &mut self.wallet { - WalletType2::Software(w) => { - w.sign_challenge(self.account_index, challenge, destination) - } - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.sign_challenge(self.account_index, challenge, destination), - } - .map_err(ControllerError::WalletError) + self.wallet + .sign_challenge(self.account_index, challenge, destination) + .map_err(ControllerError::WalletError) } pub fn add_unconfirmed_tx(&mut self, tx: SignedTransaction) -> Result<(), ControllerError> { - match &mut self.wallet { - WalletType2::Software(w) => w.add_unconfirmed_tx(tx, self.wallet_events), - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => w.add_unconfirmed_tx(tx, self.wallet_events), - } - .map_err(ControllerError::WalletError) + self.wallet + .add_unconfirmed_tx(tx, self.wallet_events) + .map_err(ControllerError::WalletError) } async fn get_current_and_consolidation_fee_rate( @@ -1416,16 +1136,9 @@ where &mut self, tx: SignedTransaction, ) -> Result> { - match &mut self.wallet { - WalletType2::Software(w) => { - w.add_account_unconfirmed_tx(self.account_index, tx.clone(), self.wallet_events) - } - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => { - w.add_account_unconfirmed_tx(self.account_index, tx.clone(), self.wallet_events) - } - } - .map_err(ControllerError::WalletError)?; + self.wallet + .add_account_unconfirmed_tx(self.account_index, &tx, self.wallet_events) + .map_err(ControllerError::WalletError)?; self.rpc_client .submit_transaction(tx.clone(), Default::default()) @@ -1454,7 +1167,7 @@ where tx_maker: F, ) -> Result> where - F: FnOnce(FeeRate, FeeRate, &mut WalletType2, U31) -> Result, + F: FnOnce(FeeRate, FeeRate, &mut RuntimeWallet, U31) -> Result, ControllerError: From, { let (current_fee_rate, consolidate_fee_rate) = @@ -1476,29 +1189,22 @@ where F: FnOnce( FeeRate, FeeRate, - &mut WalletType2, + &mut RuntimeWallet, U31, &UnconfirmedTokenInfo, ) -> WalletResult, >( &mut self, - token_info: &RPCTokenInfo, + token_info: RPCTokenInfo, tx_maker: F, ) -> Result> { // make sure we can use the token before create an tx using it let token_freezable_info = match token_info { - RPCTokenInfo::FungibleToken(token_info) => match &self.wallet { - WalletType2::Software(w) => { - w.get_token_unconfirmed_info(self.account_index, token_info) - } - #[cfg(feature = "trezor")] - WalletType2::Trezor(w) => { - w.get_token_unconfirmed_info(self.account_index, token_info) - } + RPCTokenInfo::FungibleToken(token_info) => { + self.wallet.get_token_unconfirmed_info(self.account_index, token_info)? } - .map_err(ControllerError::WalletError)?, RPCTokenInfo::NonFungibleToken(info) => { - UnconfirmedTokenInfo::NonFungibleToken(info.token_id) + UnconfirmedTokenInfo::NonFungibleToken(info.token_id, info.as_ref().into()) } }; @@ -1521,7 +1227,12 @@ where /// e.g. newly issued token, nft or delegation id async fn create_and_send_tx_with_id< ID, - F: FnOnce(FeeRate, FeeRate, &mut WalletType2, U31) -> WalletResult<(ID, SignedTransaction)>, + F: FnOnce( + FeeRate, + FeeRate, + &mut RuntimeWallet, + U31, + ) -> WalletResult<(ID, SignedTransaction)>, >( &mut self, tx_maker: F, @@ -1540,16 +1251,4 @@ where let tx_id = self.broadcast_to_mempool_if_needed(tx).await?; Ok((tx_id, id)) } - - async fn fetch_utxo(&self, input: &UtxoOutPoint) -> Result> { - let utxo = self - .rpc_client - .get_utxo(input.clone()) - .await - .map_err(ControllerError::NodeCallError)?; - - utxo.ok_or(ControllerError::WalletError(WalletError::CannotFindUtxo( - input.clone(), - ))) - } } diff --git a/wallet/wallet-controller/src/types/mod.rs b/wallet/wallet-controller/src/types/mod.rs index e346b3ccc5..ed6e307316 100644 --- a/wallet/wallet-controller/src/types/mod.rs +++ b/wallet/wallet-controller/src/types/mod.rs @@ -176,7 +176,7 @@ impl WalletTypeArgs { Self::Trezor => WalletType::Trezor, } } - pub fn user_supplied_menmonic(&self) -> bool { + pub fn user_supplied_mnemonic(&self) -> bool { match self { Self::Software { mnemonic, diff --git a/wallet/wallet-controller/src/types/transaction.rs b/wallet/wallet-controller/src/types/transaction.rs index a1aab80247..df7d953ec1 100644 --- a/wallet/wallet-controller/src/types/transaction.rs +++ b/wallet/wallet-controller/src/types/transaction.rs @@ -13,11 +13,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use common::chain::{ - partially_signed_transaction::PartiallySignedTransaction, SignedTransaction, Transaction, -}; +use common::chain::{SignedTransaction, Transaction}; use serialization::hex_encoded::HexEncoded; -use wallet_types::signature_status::SignatureStatus; +use wallet_types::{ + partially_signed_transaction::PartiallySignedTransaction, signature_status::SignatureStatus, +}; use super::Balances; diff --git a/wallet/wallet-node-client/src/handles_client/mod.rs b/wallet/wallet-node-client/src/handles_client/mod.rs index 4cfc53c0f5..d0c07da7ea 100644 --- a/wallet/wallet-node-client/src/handles_client/mod.rs +++ b/wallet/wallet-node-client/src/handles_client/mod.rs @@ -20,7 +20,7 @@ use chainstate::{BlockSource, ChainInfo, ChainstateError, ChainstateHandle}; use common::{ chain::{ tokens::{RPCTokenInfo, TokenId}, - Block, DelegationId, GenBlock, PoolId, SignedTransaction, Transaction, + Block, DelegationId, Destination, GenBlock, PoolId, SignedTransaction, Transaction, }, primitives::{time::Time, Amount, BlockHeight, Id}, }; @@ -195,6 +195,18 @@ impl NodeInterface for WalletHandlesClient { Ok(result) } + async fn get_pool_decommission_destination( + &self, + pool_id: PoolId, + ) -> Result, Self::Error> { + let result = self + .chainstate + .call(move |this| this.get_stake_pool_data(pool_id)) + .await?? + .map(|data| data.decommission_destination().clone()); + Ok(result) + } + async fn get_delegation_share( &self, pool_id: PoolId, diff --git a/wallet/wallet-node-client/src/node_traits.rs b/wallet/wallet-node-client/src/node_traits.rs index 439d1241fa..5661332b82 100644 --- a/wallet/wallet-node-client/src/node_traits.rs +++ b/wallet/wallet-node-client/src/node_traits.rs @@ -19,8 +19,8 @@ use chainstate::ChainInfo; use common::{ chain::{ tokens::{RPCTokenInfo, TokenId}, - Block, DelegationId, GenBlock, PoolId, SignedTransaction, Transaction, TxOutput, - UtxoOutPoint, + Block, DelegationId, Destination, GenBlock, PoolId, SignedTransaction, Transaction, + TxOutput, UtxoOutPoint, }, primitives::{time::Time, Amount, BlockHeight, Id}, }; @@ -65,6 +65,10 @@ pub trait NodeInterface { ) -> Result, BlockHeight)>, Self::Error>; async fn get_stake_pool_balance(&self, pool_id: PoolId) -> Result, Self::Error>; async fn get_staker_balance(&self, pool_id: PoolId) -> Result, Self::Error>; + async fn get_pool_decommission_destination( + &self, + pool_id: PoolId, + ) -> Result, Self::Error>; async fn get_delegation_share( &self, pool_id: PoolId, diff --git a/wallet/wallet-node-client/src/rpc_client/client_impl.rs b/wallet/wallet-node-client/src/rpc_client/client_impl.rs index b0ba8dc2d3..d628963aa9 100644 --- a/wallet/wallet-node-client/src/rpc_client/client_impl.rs +++ b/wallet/wallet-node-client/src/rpc_client/client_impl.rs @@ -21,8 +21,8 @@ use common::{ address::Address, chain::{ tokens::{RPCTokenInfo, TokenId}, - Block, DelegationId, GenBlock, PoolId, SignedTransaction, Transaction, TxOutput, - UtxoOutPoint, + Block, DelegationId, Destination, GenBlock, PoolId, SignedTransaction, Transaction, + TxOutput, UtxoOutPoint, }, primitives::{time::Time, Amount, BlockHeight, Id}, }; @@ -141,6 +141,19 @@ impl NodeInterface for NodeRpcClient { .map_err(NodeRpcError::ResponseError) } + async fn get_pool_decommission_destination( + &self, + pool_id: PoolId, + ) -> Result, Self::Error> { + let pool_address = Address::new(&self.chain_config, pool_id)?; + ChainstateRpcClient::pool_decommission_destination( + &self.http_client, + pool_address.into_string(), + ) + .await + .map_err(NodeRpcError::ResponseError) + } + async fn get_delegation_share( &self, pool_id: PoolId, diff --git a/wallet/wallet-node-client/src/rpc_client/cold_wallet_client.rs b/wallet/wallet-node-client/src/rpc_client/cold_wallet_client.rs index be66c34c10..5c864a2407 100644 --- a/wallet/wallet-node-client/src/rpc_client/cold_wallet_client.rs +++ b/wallet/wallet-node-client/src/rpc_client/cold_wallet_client.rs @@ -20,7 +20,7 @@ use chainstate::ChainInfo; use common::{ chain::{ tokens::{RPCTokenInfo, TokenId}, - Block, DelegationId, GenBlock, PoolId, SignedTransaction, Transaction, + Block, DelegationId, Destination, GenBlock, PoolId, SignedTransaction, Transaction, }, primitives::{time::Time, Amount, BlockHeight, Id}, }; @@ -118,6 +118,13 @@ impl NodeInterface for ColdWalletClient { Err(ColdWalletRpcError::NotAvailable) } + async fn get_pool_decommission_destination( + &self, + _pool_id: PoolId, + ) -> Result, Self::Error> { + Err(ColdWalletRpcError::NotAvailable) + } + async fn get_delegation_share( &self, _pool_id: PoolId, diff --git a/wallet/wallet-node-client/src/rpc_client/mod.rs b/wallet/wallet-node-client/src/rpc_client/mod.rs index 3e646cc750..c24021e9b5 100644 --- a/wallet/wallet-node-client/src/rpc_client/mod.rs +++ b/wallet/wallet-node-client/src/rpc_client/mod.rs @@ -20,6 +20,7 @@ use std::sync::Arc; use common::address::AddressError; use common::chain::ChainConfig; +use common::primitives::per_thousand::PerThousandParseError; use rpc::new_http_client; use rpc::ClientError; use rpc::RpcAuthData; @@ -39,6 +40,8 @@ pub enum NodeRpcError { ResponseError(ClientError), #[error("Address error: {0}")] AddressError(#[from] AddressError), + #[error("PerThousand parse error: {0}")] + PerThousandParseError(#[from] PerThousandParseError), } #[derive(Clone, Debug)] diff --git a/wallet/wallet-rpc-client/src/handles_client/mod.rs b/wallet/wallet-rpc-client/src/handles_client/mod.rs index a2ed7b683a..b3b09ad0bb 100644 --- a/wallet/wallet-rpc-client/src/handles_client/mod.rs +++ b/wallet/wallet-rpc-client/src/handles_client/mod.rs @@ -19,9 +19,8 @@ use chainstate::ChainInfo; use common::{ address::{dehexify::dehexify_all_addresses, AddressError}, chain::{ - block::timestamp::BlockTimestamp, partially_signed_transaction::PartiallySignedTransaction, - tokens::IsTokenUnfreezable, Block, GenBlock, SignedTransaction, Transaction, TxOutput, - UtxoOutPoint, + block::timestamp::BlockTimestamp, tokens::IsTokenUnfreezable, Block, GenBlock, + SignedTransaction, Transaction, TxOutput, UtxoOutPoint, }, primitives::{BlockHeight, DecimalAmount, Id, Idable, H256}, }; @@ -53,8 +52,8 @@ use wallet_rpc_lib::{ RpcError, WalletRpc, }; use wallet_types::{ - seed_phrase::StoreSeedPhrase, signature_status::SignatureStatus, utxo_types::UtxoTypes, - with_locked::WithLocked, + partially_signed_transaction::PartiallySignedTransaction, seed_phrase::StoreSeedPhrase, + signature_status::SignatureStatus, utxo_types::UtxoTypes, with_locked::WithLocked, }; use crate::wallet_rpc_traits::{PartialOrSignedTx, SignRawTransactionResult, WalletInterface}; diff --git a/wallet/wallet-rpc-client/src/rpc_client/client_impl.rs b/wallet/wallet-rpc-client/src/rpc_client/client_impl.rs index c6717d5e2d..ea2dd531d9 100644 --- a/wallet/wallet-rpc-client/src/rpc_client/client_impl.rs +++ b/wallet/wallet-rpc-client/src/rpc_client/client_impl.rs @@ -22,8 +22,8 @@ use super::{ClientWalletRpc, WalletRpcError}; use chainstate::ChainInfo; use common::{ chain::{ - block::timestamp::BlockTimestamp, partially_signed_transaction::PartiallySignedTransaction, - Block, GenBlock, SignedTransaction, Transaction, TxOutput, UtxoOutPoint, + block::timestamp::BlockTimestamp, Block, GenBlock, SignedTransaction, Transaction, + TxOutput, UtxoOutPoint, }, primitives::{BlockHeight, DecimalAmount, Id}, }; @@ -49,7 +49,9 @@ use wallet_rpc_lib::{ }, ColdWalletRpcClient, WalletRpcClient, }; -use wallet_types::with_locked::WithLocked; +use wallet_types::{ + partially_signed_transaction::PartiallySignedTransaction, with_locked::WithLocked, +}; #[async_trait::async_trait] impl WalletInterface for ClientWalletRpc { diff --git a/wallet/wallet-rpc-client/src/wallet_rpc_traits.rs b/wallet/wallet-rpc-client/src/wallet_rpc_traits.rs index 42adff6a4d..5cbf2e54e9 100644 --- a/wallet/wallet-rpc-client/src/wallet_rpc_traits.rs +++ b/wallet/wallet-rpc-client/src/wallet_rpc_traits.rs @@ -18,8 +18,8 @@ use std::{collections::BTreeMap, num::NonZeroUsize, path::PathBuf}; use chainstate::ChainInfo; use common::{ chain::{ - block::timestamp::BlockTimestamp, partially_signed_transaction::PartiallySignedTransaction, - Block, GenBlock, SignedTransaction, Transaction, TxOutput, UtxoOutPoint, + block::timestamp::BlockTimestamp, Block, GenBlock, SignedTransaction, Transaction, + TxOutput, UtxoOutPoint, }, primitives::{BlockHeight, DecimalAmount, Id}, }; @@ -40,7 +40,9 @@ use wallet_rpc_lib::types::{ SendTokensFromMultisigAddressResult, StakePoolBalance, StakingStatus, StandaloneAddressWithDetails, TokenMetadata, TxOptionsOverrides, VrfPublicKeyInfo, }; -use wallet_types::with_locked::WithLocked; +use wallet_types::{ + partially_signed_transaction::PartiallySignedTransaction, with_locked::WithLocked, +}; pub enum PartialOrSignedTx { Partial(PartiallySignedTransaction), diff --git a/wallet/wallet-rpc-lib/src/rpc/interface.rs b/wallet/wallet-rpc-lib/src/rpc/interface.rs index b5a5037b58..1fcb3f5275 100644 --- a/wallet/wallet-rpc-lib/src/rpc/interface.rs +++ b/wallet/wallet-rpc-lib/src/rpc/interface.rs @@ -18,9 +18,8 @@ use std::{collections::BTreeMap, num::NonZeroUsize}; use common::{ address::RpcAddress, chain::{ - block::timestamp::BlockTimestamp, tokens::TokenId, - transaction::partially_signed_transaction::PartiallySignedTransaction, Block, DelegationId, - Destination, GenBlock, PoolId, SignedTransaction, Transaction, TxOutput, + block::timestamp::BlockTimestamp, tokens::TokenId, Block, DelegationId, Destination, + GenBlock, PoolId, SignedTransaction, Transaction, TxOutput, }, primitives::{BlockHeight, Id}, }; @@ -32,7 +31,9 @@ use wallet_controller::{ types::{BlockInfo, CreatedBlockInfo, GenericTokenTransfer, SeedWithPassPhrase, WalletInfo}, ConnectedPeer, }; -use wallet_types::with_locked::WithLocked; +use wallet_types::{ + partially_signed_transaction::PartiallySignedTransaction, with_locked::WithLocked, +}; use crate::types::{ AccountArg, AddressInfo, AddressWithUsageInfo, Balances, ChainInfo, ComposedTransaction, diff --git a/wallet/wallet-rpc-lib/src/rpc/mod.rs b/wallet/wallet-rpc-lib/src/rpc/mod.rs index 5186ab4278..c37cacff5d 100644 --- a/wallet/wallet-rpc-lib/src/rpc/mod.rs +++ b/wallet/wallet-rpc-lib/src/rpc/mod.rs @@ -40,6 +40,7 @@ use wallet::{ currency_grouper::Currency, transaction_list::TransactionList, PoolData, TransactionToSign, TxInfo, }, + send_request::PoolOrTokenId, WalletError, }; @@ -50,7 +51,6 @@ use common::{ classic_multisig::ClassicMultisigChallenge, htlc::{HashedTimelockContract, HtlcSecret, HtlcSecretHash}, output_value::OutputValue, - partially_signed_transaction::PartiallySignedTransaction, signature::inputsig::arbitrary_message::{ produce_message_challenge, ArbitraryMessageSignature, }, @@ -76,7 +76,12 @@ use wallet_controller::{ UtxoType, UtxoTypes, DEFAULT_ACCOUNT_INDEX, }; use wallet_types::{ - account_info::StandaloneAddressDetails, signature_status::SignatureStatus, wallet_tx::TxData, + account_info::StandaloneAddressDetails, + partially_signed_transaction::{ + PartiallySignedTransaction, TokenAdditionalInfo, UtxoAdditionalInfo, + }, + signature_status::SignatureStatus, + wallet_tx::TxData, with_locked::WithLocked, }; @@ -839,6 +844,7 @@ where .synced_controller(account_index, config) .await? .sign_raw_transaction(tx_to_sign) + .await .map_err(RpcError::Controller) }) }) @@ -1183,6 +1189,7 @@ where let (tx, _, cur_signatures) = synced_controller .sign_raw_transaction(TransactionToSign::Partial(tx)) + .await .map_err(RpcError::Controller)?; Ok::<_, RpcError>((tx, cur_signatures, fees)) @@ -1449,12 +1456,20 @@ where self.wallet .call_async(move |controller| { Box::pin(async move { + let mut additional_utxo_infos = BTreeMap::new(); let value = match token_id { Some(token_id) => { let token_info = controller.get_token_info(token_id).await?; let amount = amount .to_amount(token_info.token_number_of_decimals()) .ok_or(RpcError::InvalidCoinAmount)?; + additional_utxo_infos.insert( + PoolOrTokenId::TokenId(token_id), + UtxoAdditionalInfo::TokenInfo(TokenAdditionalInfo { + num_decimals: token_info.token_number_of_decimals(), + ticker: token_info.token_ticker().to_vec(), + }), + ); OutputValue::TokenV1(token_id, amount) } None => { @@ -1468,7 +1483,7 @@ where controller .synced_controller(account_index, config) .await? - .create_htlc_tx(value, htlc) + .create_htlc_tx(value, htlc, &additional_utxo_infos) .await .map_err(RpcError::Controller) }) diff --git a/wallet/wallet-rpc-lib/src/rpc/server_impl.rs b/wallet/wallet-rpc-lib/src/rpc/server_impl.rs index 7a35b36655..f1946518e1 100644 --- a/wallet/wallet-rpc-lib/src/rpc/server_impl.rs +++ b/wallet/wallet-rpc-lib/src/rpc/server_impl.rs @@ -19,7 +19,6 @@ use common::{ address::dehexify::dehexify_all_addresses, chain::{ block::timestamp::BlockTimestamp, - partially_signed_transaction::PartiallySignedTransaction, tokens::{IsTokenUnfreezable, TokenId}, Block, DelegationId, Destination, GenBlock, PoolId, SignedTransaction, Transaction, TxOutput, @@ -40,7 +39,8 @@ use wallet_controller::{ ConnectedPeer, ControllerConfig, NodeInterface, UtxoState, UtxoStates, UtxoType, UtxoTypes, }; use wallet_types::{ - seed_phrase::StoreSeedPhrase, signature_status::SignatureStatus, with_locked::WithLocked, + partially_signed_transaction::PartiallySignedTransaction, seed_phrase::StoreSeedPhrase, + signature_status::SignatureStatus, with_locked::WithLocked, }; use crate::{ diff --git a/wallet/wallet-rpc-lib/src/rpc/types.rs b/wallet/wallet-rpc-lib/src/rpc/types.rs index 9d10ce11f9..0ee697c27b 100644 --- a/wallet/wallet-rpc-lib/src/rpc/types.rs +++ b/wallet/wallet-rpc-lib/src/rpc/types.rs @@ -20,7 +20,6 @@ use common::{ chain::{ block::timestamp::BlockTimestamp, classic_multisig::ClassicMultisigChallengeError, - partially_signed_transaction::PartiallySignedTransaction, signature::DestinationSigError, timelock::OutputTimeLock, tokens::{self, IsTokenFreezable, Metadata, TokenCreator, TokenId}, @@ -56,7 +55,9 @@ pub use wallet_controller::types::{ }; pub use wallet_controller::{ControllerConfig, NodeInterface}; use wallet_controller::{UtxoState, UtxoType}; -use wallet_types::signature_status::SignatureStatus; +use wallet_types::{ + partially_signed_transaction::PartiallySignedTransaction, signature_status::SignatureStatus, +}; use crate::service::SubmitError;