From be9258566121082796e5126c22356b2664cfbe61 Mon Sep 17 00:00:00 2001 From: Uncle Stretch Date: Sun, 8 Dec 2024 19:05:37 +0300 Subject: [PATCH] balance transfer added Signed-off-by: Uncle Stretch --- src/action.rs | 8 +- src/components/explorer/finalized_block.rs | 4 +- src/components/wallet/accounts.rs | 12 + .../wallet/add_address_book_record.rs | 10 +- src/components/wallet/address_book.rs | 34 ++- src/components/wallet/mod.rs | 15 +- src/components/wallet/transfer.rs | 230 ++++++++++++++++-- src/network/mod.rs | 63 ++++- src/network/predefinded_calls.rs | 55 ++++- src/network/subscriptions.rs | 4 +- 10 files changed, 382 insertions(+), 53 deletions(-) diff --git a/src/action.rs b/src/action.rs index fd319e7..d600493 100644 --- a/src/action.rs +++ b/src/action.rs @@ -8,8 +8,6 @@ use crate::{ types::{SystemAccount, ActionLevel, EraInfo, CasperExtrinsicDetails}, }; -use subxt::utils::AccountId32; - #[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)] pub enum Action { Tick, @@ -26,10 +24,12 @@ pub enum Action { SetActiveScreen(crate::app::Mode), UsedExplorerBlock(Option), UsedExplorerLog(Option), - UsedAccount(AccountId32), + UsedAccount(String), NewAccount(String), NewAddressBookRecord(String, String), + ClosePopup, + BalanceRequest([u8; 32], bool), BalanceResponse([u8; 32], SystemAccount), BalanceSetActive(Option), @@ -38,7 +38,9 @@ pub enum Action { RenameAddressBookRecord(String), UpdateAccountName(String), UpdateAddressBookRecord(String), + TransferTo(String), + TransferBalance(String, [u8; 32], u128), WalletLog(String, ActionLevel), NewBestBlock(u32), diff --git a/src/components/explorer/finalized_block.rs b/src/components/explorer/finalized_block.rs index 589e2af..0e51386 100644 --- a/src/components/explorer/finalized_block.rs +++ b/src/components/explorer/finalized_block.rs @@ -69,7 +69,7 @@ impl Component for FinalizedBlock { .title_alignment(Alignment::Right) .title_style(self.palette.create_title_style(false)) .padding(Padding::new(0, 0, height.saturating_sub(2) / 2, 0)) - .title("Latest")) + .title("Finalized")) .alignment(Alignment::Center) .wrap(Wrap { trim: true }); frame.render_widget(paragraph, place); @@ -88,7 +88,7 @@ impl Component for FinalizedBlock { .border_type(border_type) .title_alignment(Alignment::Right) .title_style(self.palette.create_title_style(false)) - .title("Latest")) + .title("Finalized")) .alignment(Alignment::Center) .wrap(Wrap { trim: true }); frame.render_widget(paragraph, place); diff --git a/src/components/wallet/accounts.rs b/src/components/wallet/accounts.rs index cfdcc46..f2f877e 100644 --- a/src/components/wallet/accounts.rs +++ b/src/components/wallet/accounts.rs @@ -76,6 +76,13 @@ impl Accounts { } } + fn set_used_account(&mut self, index: usize) { + if let Some(action_tx) = &self.action_tx { + let _ = action_tx.send(Action::UsedAccount( + self.wallet_keys[index].seed.clone())); + } + } + fn log_event(&mut self, message: String, level: ActionLevel) { if let Some(action_tx) = &self.action_tx { let _ = action_tx.send(Action::WalletLog(message, level)); @@ -273,6 +280,7 @@ impl Accounts { self.table_state.select(Some(0)); self.scroll_state = self.scroll_state.content_length(self.wallet_keys.len()); self.set_balance_active(0); + self.set_used_account(0); Ok(()) } @@ -281,6 +289,7 @@ impl Accounts { self.table_state.select(Some(0)); self.scroll_state = self.scroll_state.position(0); self.set_balance_active(0); + self.set_used_account(0); } } @@ -298,6 +307,7 @@ impl Accounts { self.table_state.select(Some(i)); self.scroll_state = self.scroll_state.position(i); self.set_balance_active(i); + self.set_used_account(i); } fn last_row(&mut self) { @@ -306,6 +316,7 @@ impl Accounts { self.table_state.select(Some(last)); self.scroll_state = self.scroll_state.position(last); self.set_balance_active(last); + self.set_used_account(last); } } @@ -323,6 +334,7 @@ impl Accounts { self.table_state.select(Some(i)); self.scroll_state = self.scroll_state.position(i); self.set_balance_active(i); + self.set_used_account(i); } fn prepare_u128(&self, value: u128, after: usize, ticker: Option<&str>) -> String { diff --git a/src/components/wallet/add_address_book_record.rs b/src/components/wallet/add_address_book_record.rs index 5a66480..25e6285 100644 --- a/src/components/wallet/add_address_book_record.rs +++ b/src/components/wallet/add_address_book_record.rs @@ -112,10 +112,12 @@ impl PartialComponent for AddAddressBookRecord { match current_tab { CurrentTab::AddAddressBookRecord => self.is_active = true, _ => { - self.is_active = false; - self.name = Input::new(String::new()); - self.address = Input::new(String::new()); - self.name_or_address = NameOrAddress::Name; + if self.is_active { + self.is_active = false; + self.name = Input::new(String::new()); + self.address = Input::new(String::new()); + self.name_or_address = NameOrAddress::Name; + } }, }; } diff --git a/src/components/wallet/address_book.rs b/src/components/wallet/address_book.rs index 0993914..b69cfec 100644 --- a/src/components/wallet/address_book.rs +++ b/src/components/wallet/address_book.rs @@ -74,6 +74,15 @@ impl AddressBook { } } + fn send_transfer_to(&mut self) { + if let Some(action_tx) = &self.action_tx { + if let Some(index) = self.table_state.selected() { + let _ = action_tx.send(Action::TransferTo( + self.address_book[index].address.clone())); + } + } + } + fn send_balance_request(&mut self, account_id: [u8; 32], remove: bool) { if let Some(network_tx) = &self.network_tx { let _ = network_tx.send(Action::BalanceRequest(account_id, remove)); @@ -363,17 +372,20 @@ impl Component for AddressBook { fn handle_key_event(&mut self, key: KeyEvent) -> Result> { - match key.code { - KeyCode::Up | KeyCode::Char('k') if self.is_active => self.previous_row(), - KeyCode::Down | KeyCode::Char('j') if self.is_active => self.next_row(), - KeyCode::Char('g') if self.is_active => self.first_row(), - KeyCode::Char('G') if self.is_active => self.last_row(), - KeyCode::Char('K') if self.is_active => self.swap_up(), - KeyCode::Char('J') if self.is_active => self.swap_down(), - KeyCode::Char('D') if self.is_active => self.delete_row(), - KeyCode::Char('R') if self.is_active => self.update_address_book_record(), - _ => {}, - }; + if self.is_active { + match key.code { + KeyCode::Up | KeyCode::Char('k') => self.previous_row(), + KeyCode::Down | KeyCode::Char('j') => self.next_row(), + KeyCode::Char('g') => self.first_row(), + KeyCode::Char('G') => self.last_row(), + KeyCode::Char('K') => self.swap_up(), + KeyCode::Char('J') => self.swap_down(), + KeyCode::Char('D') => self.delete_row(), + KeyCode::Char('R') => self.update_address_book_record(), + KeyCode::Enter => self.send_transfer_to(), + _ => {}, + }; + } Ok(None) } diff --git a/src/components/wallet/mod.rs b/src/components/wallet/mod.rs index 4853cca..3f4d934 100644 --- a/src/components/wallet/mod.rs +++ b/src/components/wallet/mod.rs @@ -43,6 +43,7 @@ pub enum CurrentTab { AddAddressBookRecord, RenameAccount, RenameAddressBookRecord, + Transfer, } pub trait PartialComponent: Component { @@ -66,11 +67,11 @@ impl Default for Wallet { Box::new(Balance::default()), Box::new(AddressBook::default()), Box::new(EventLogs::default()), - Box::new(Transfer::default()), Box::new(AddAccount::default()), Box::new(RenameAccount::default()), Box::new(AddAddressBookRecord::default()), Box::new(RenameAddressBookRecord::default()), + Box::new(Transfer::default()), ], } } @@ -125,6 +126,7 @@ impl Component for Wallet { CurrentTab::AddAccount | CurrentTab::RenameAccount | CurrentTab::RenameAddressBookRecord | + CurrentTab::Transfer | CurrentTab::AddAddressBookRecord => match key.code { KeyCode::Esc => { self.current_tab = CurrentTab::Accounts; @@ -159,7 +161,13 @@ impl Component for Wallet { component.set_active(self.current_tab.clone()); } }, - KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right => { + KeyCode::Char('T') => { + self.current_tab = CurrentTab::Transfer; + for component in self.components.iter_mut() { + component.set_active(self.current_tab.clone()); + } + }, + KeyCode::Char('l') | KeyCode::Right => { self.move_right(); for component in self.components.iter_mut() { component.set_active(self.current_tab.clone()); @@ -186,11 +194,12 @@ impl Component for Wallet { Action::SetActiveScreen(Mode::Wallet) => self.is_active = true, Action::UpdateAccountName(_) | Action::NewAccount(_) => self.current_tab = CurrentTab::Accounts, - Action::UpdateAddressBookRecord(_) | Action::NewAddressBookRecord(_, _) => + Action::UpdateAddressBookRecord(_) | Action::NewAddressBookRecord(_, _) | Action::ClosePopup => self.current_tab = CurrentTab::AddressBook, Action::RenameAccount(_) => self.current_tab = CurrentTab::RenameAccount, Action::RenameAddressBookRecord(_) => self.current_tab = CurrentTab::RenameAddressBookRecord, + Action::TransferTo(_) => self.current_tab = CurrentTab::Transfer, _ => {} } for component in self.components.iter_mut() { diff --git a/src/components/wallet/transfer.rs b/src/components/wallet/transfer.rs index 197c0ba..260adb6 100644 --- a/src/components/wallet/transfer.rs +++ b/src/components/wallet/transfer.rs @@ -1,10 +1,15 @@ use color_eyre::Result; -use crossterm::event::KeyEvent; +use crossterm::event::{KeyCode, KeyEvent, KeyEventKind}; use ratatui::{ - layout::{Constraint, Flex, Layout, Rect}, - widgets::{Block, Clear}, + layout::{Alignment, Constraint, Flex, Layout, Position, Rect}, + widgets::{Block, Clear, Paragraph}, Frame, - prelude::Stylize, +}; +use tokio::sync::mpsc::UnboundedSender; +use std::sync::mpsc::Sender; + +use subxt::ext::sp_core::crypto::{ + ByteArray, Ss58Codec, Ss58AddressFormat, AccountId32, }; use super::{Component, PartialComponent, CurrentTab}; @@ -12,12 +17,26 @@ use crate::{ action::Action, config::Config, palette::StylePalette, + types::ActionLevel, + widgets::{Input, InputRequest}, }; +#[derive(Debug)] +enum ReceiverOrAmount { + Receiver, + Amount, +} + #[derive(Debug)] pub struct Transfer { + network_tx: Option>, + action_tx: Option>, + receiver_or_amount: ReceiverOrAmount, + sender: String, + receiver: Input, + amount: Input, palette: StylePalette, - is_shown: bool, + is_active: bool, } impl Default for Transfer { @@ -29,17 +48,136 @@ impl Default for Transfer { impl Transfer { pub fn new() -> Self { Self { - is_shown: false, + sender: Default::default(), + network_tx: None, + action_tx: None, + receiver_or_amount: ReceiverOrAmount::Receiver, + receiver: Input::new(String::new()), + amount: Input::new(String::new()), palette: StylePalette::default(), + is_active: false, + } + } + + fn log_event(&mut self, message: String, level: ActionLevel) { + if let Some(action_tx) = &self.action_tx { + let _ = action_tx.send(Action::WalletLog(message, level)); + } + } + + fn submit_message(&mut self) { + match self.receiver_or_amount { + ReceiverOrAmount::Receiver => + self.receiver_or_amount = ReceiverOrAmount::Amount, + ReceiverOrAmount::Amount => if let Some(network_tx) = &self.network_tx { + match AccountId32::from_ss58check_with_version(self.receiver.value()) { + Ok((_account_id, format)) if format != Ss58AddressFormat::custom(1996) => { + self.log_event( + format!("provided public address for {} is not part of Casper/Ghost ecosystem", self.receiver.value()), + ActionLevel::Error); + }, + Ok((account_id, format)) if format == Ss58AddressFormat::custom(1996) => { + let seed_vec = account_id.to_raw_vec(); + let mut account_id = [0u8; 32]; + account_id.copy_from_slice(&seed_vec); + + match self.amount.value().parse::() { + Ok(value) => { + let amount = (value * 1_000_000_000_000_000_000.0) as u128; + let _ = network_tx.send(Action::TransferBalance( + self.sender.clone(), account_id, amount)); + if let Some(action_tx) = &self.action_tx { + let _ = action_tx.send(Action::ClosePopup); + } + }, + Err(err) => self.log_event( + format!("invalid amount, error: {err}"), ActionLevel::Error), + } + }, + _ => self.log_event( + format!("could not create valid account id from {}", self.receiver.value()), + ActionLevel::Error), + } + } + } + } + + fn enter_char(&mut self, new_char: char) { + match self.receiver_or_amount { + ReceiverOrAmount::Receiver => { + let _ = self.receiver.handle(InputRequest::InsertChar(new_char)); + }, + ReceiverOrAmount::Amount => { + let is_separator_needed = !self.amount.value().contains('.') && new_char == '.'; + if new_char.is_digit(10) || is_separator_needed { + let _ = self.amount.handle(InputRequest::InsertChar(new_char)); + } + } + } + } + + fn delete_char(&mut self) { + match self.receiver_or_amount { + ReceiverOrAmount::Receiver => { + let _ = self.receiver.handle(InputRequest::DeletePrevChar); + }, + ReceiverOrAmount::Amount => { + let _ = self.amount.handle(InputRequest::DeletePrevChar); + } + } + } + + fn move_cursor_right(&mut self) { + match self.receiver_or_amount { + ReceiverOrAmount::Receiver => { + let _ = self.receiver.handle(InputRequest::GoToNextChar); + }, + ReceiverOrAmount::Amount => { + let _ = self.amount.handle(InputRequest::GoToNextChar); + } + } + } + + fn move_cursor_left(&mut self) { + match self.receiver_or_amount { + ReceiverOrAmount::Receiver => { + let _ = self.receiver.handle(InputRequest::GoToPrevChar); + }, + ReceiverOrAmount::Amount => { + let _ = self.amount.handle(InputRequest::GoToPrevChar); + } } } } impl PartialComponent for Transfer { - fn set_active(&mut self, _current_tab: CurrentTab) {} + fn set_active(&mut self, current_tab: CurrentTab) { + match current_tab { + CurrentTab::Transfer => self.is_active = true, + _ => { + if self.is_active { + self.is_active = false; + self.receiver = Input::new(String::new()); + self.amount = Input::new(String::new()); + self.receiver_or_amount = ReceiverOrAmount::Receiver; + } + }, + }; + } } + impl Component for Transfer { + fn register_network_handler(&mut self, tx: Sender) -> Result<()> { + self.network_tx = Some(tx); + Ok(()) + } + + fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { + self.action_tx = Some(tx); + Ok(()) + } + fn register_config_handler(&mut self, config: Config) -> Result<()> { if let Some(style) = config.styles.get(&crate::app::Mode::Wallet) { self.palette.with_normal_style(style.get("normal_style").copied()); @@ -52,28 +190,84 @@ impl Component for Transfer { } fn handle_key_event(&mut self, key: KeyEvent) -> Result> { - match key.code { - _ => {}, - }; + if self.is_active && key.kind == KeyEventKind::Press { + match key.code { + KeyCode::Up => self.receiver_or_amount = ReceiverOrAmount::Receiver, + KeyCode::Down => self.receiver_or_amount = ReceiverOrAmount::Amount, + KeyCode::Enter => self.submit_message(), + KeyCode::Char(to_insert) => self.enter_char(to_insert), + KeyCode::Backspace => self.delete_char(), + KeyCode::Left => self.move_cursor_left(), + KeyCode::Right => self.move_cursor_right(), + KeyCode::Esc => self.is_active = false, + _ => {}, + }; + } Ok(None) } fn update(&mut self, action: Action) -> Result> { match action { + Action::UsedAccount(seed) => self.sender = seed, + Action::TransferTo(who) => { + self.receiver = Input::new(who); + self.receiver_or_amount = ReceiverOrAmount::Amount; + } _ => {} }; Ok(None) } fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { - if self.is_shown { - let block = Block::bordered().on_red().title("Transfer"); - let v = Layout::vertical([Constraint::Max(10)]).flex(Flex::Center); - let h = Layout::horizontal([Constraint::Max(55)]).flex(Flex::Center); - let [area] = v.areas(area); - let [area] = h.areas(area); - frame.render_widget(Clear, area); - frame.render_widget(block, area); + if self.is_active { + let size = area.as_size(); + let receiver_area = Rect::new(size.width / 2, size.height / 2, 50, 3); + let amount_area = Rect::new(size.width / 2, size.height / 2 + 3, 50, 3); + let (border_style, border_type) = self.palette.create_popup_style(); + + let input_receiver = Paragraph::new(self.receiver.value()) + .block(Block::bordered() + .border_style(border_style) + .border_type(border_type) + .title_style(self.palette.create_popup_title_style()) + .title_alignment(Alignment::Right) + .title("Receiver")); + + let input_amount = Paragraph::new(self.amount.value()) + .block(Block::bordered() + .border_style(border_style) + .border_type(border_type) + .title_style(self.palette.create_popup_title_style()) + .title_alignment(Alignment::Right) + .title("Amount to send")); + + let v = Layout::vertical([Constraint::Max(3)]).flex(Flex::Center); + let h = Layout::horizontal([Constraint::Max(50)]).flex(Flex::Center); + + let [receiver_area] = v.areas(receiver_area); + let [receiver_area] = h.areas(receiver_area); + let [amount_area] = v.areas(amount_area); + let [amount_area] = h.areas(amount_area); + + frame.render_widget(Clear, receiver_area); + frame.render_widget(Clear, amount_area); + frame.render_widget(input_receiver, receiver_area); + frame.render_widget(input_amount, amount_area); + + match self.receiver_or_amount { + ReceiverOrAmount::Receiver => { + frame.set_cursor_position(Position::new( + receiver_area.x + self.receiver.cursor() as u16 + 1, + receiver_area.y + 1 + )); + }, + ReceiverOrAmount::Amount => { + frame.set_cursor_position(Position::new( + amount_area.x + self.amount.cursor() as u16 + 1, + amount_area.y + 1 + )); + } + } } Ok(()) } diff --git a/src/network/mod.rs b/src/network/mod.rs index ac9d4ce..29c8640 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -4,9 +4,10 @@ use subxt::{ backend::{ legacy::LegacyRpcMethods, rpc::RpcClient, - }, + }, + tx::{TxProgress, TxStatus}, utils::H256, - OnlineClient + OnlineClient, }; mod legacy_rpc_calls; @@ -14,6 +15,7 @@ mod predefinded_calls; mod subscriptions; use crate::{ + types::ActionLevel, action::Action, casper::CasperConfig, }; @@ -27,7 +29,8 @@ pub struct Network { rpc_client: RpcClient, best_hash: Option, finalized_hash: Option, - accounts_to_watch: std::collections::HashSet<[u8; 32]> + accounts_to_watch: std::collections::HashSet<[u8; 32]>, + transactions_to_watch: Vec>>, } impl Network { @@ -45,6 +48,7 @@ impl Network { best_hash: None, finalized_hash: None, accounts_to_watch: Default::default(), + transactions_to_watch: Default::default(), } } @@ -59,6 +63,41 @@ impl Network { }, Action::NewFinalizedHash(hash) => { self.finalized_hash = Some(hash); + let length = self.transactions_to_watch.len(); + for i in (0..length).rev() { + let pending_tx = &mut self.transactions_to_watch[i]; + let ext_hash = pending_tx.extrinsic_hash(); + match (*pending_tx).next().await { + Some(Ok(status)) => { + match status { + TxStatus::Validated => self.action_tx.send(Action::WalletLog(format!("transaction {} is part of future queue", ext_hash), ActionLevel::Info))?, + TxStatus::Broadcasted { num_peers } => self.action_tx.send(Action::WalletLog(format!("transaction {} has been broardcasted to {} nodes", ext_hash, num_peers), ActionLevel::Info))?, + TxStatus::NoLongerInBestBlock => self.action_tx.send(Action::WalletLog(format!("transaction {} is no longer in a best block", ext_hash), ActionLevel::Warn))?, + TxStatus::InBestBlock(b) => self.action_tx.send(Action::WalletLog(format!("transaction {} included in the block header {}", b.extrinsic_hash(), b.block_hash()), ActionLevel::Info))?, + TxStatus::InFinalizedBlock(b) => { + self.action_tx.send(Action::WalletLog(format!("transaction {} has been finalized in block header {}", b.extrinsic_hash(), b.block_hash()), ActionLevel::Info))?; + self.transactions_to_watch.remove(i); + } + TxStatus::Error { message } => { + self.action_tx.send(Action::WalletLog(format!("transaction {} error, something get wrong: {message}", ext_hash), ActionLevel::Error))?; + self.transactions_to_watch.remove(i); + } + TxStatus::Invalid { message } => { + self.action_tx.send(Action::WalletLog(format!("transaction {} invalid: {message}", ext_hash), ActionLevel::Error))?; + self.transactions_to_watch.remove(i); + } + TxStatus::Dropped { message } => { + self.action_tx.send(Action::WalletLog(format!("transaction {} was dropped: {message}", ext_hash), ActionLevel::Error))?; + self.transactions_to_watch.remove(i); + } + } + }, + _ => { + self.action_tx.send(Action::WalletLog(format!("transaction {} was dropped", ext_hash), ActionLevel::Error))?; + self.transactions_to_watch.remove(i); + } + } + } Ok(()) }, Action::GetSystemHealth => legacy_rpc_calls::get_system_health(&self.action_tx, &self.legacy_client_api).await, @@ -83,6 +122,24 @@ impl Network { predefinded_calls::get_balance(&self.action_tx, &self.online_client_api, &account_id).await } } + Action::TransferBalance(sender, receiver, amount) => { + let sender: [u8; 32] = hex::decode(sender) + .expect("stored seed is valid hex string; qed") + .as_slice() + .try_into() + .expect("stored seed is valid length; qed"); + + if let Ok(tx_progress) = predefinded_calls::transfer_balance( + &self.action_tx, + &self.online_client_api, + &sender, + &receiver, + &amount, + ).await { + self.transactions_to_watch.push(tx_progress); + } + Ok(()) + } _ => Ok(()) } } diff --git a/src/network/predefinded_calls.rs b/src/network/predefinded_calls.rs index c18cb26..7fbfee2 100644 --- a/src/network/predefinded_calls.rs +++ b/src/network/predefinded_calls.rs @@ -1,21 +1,27 @@ use tokio::sync::mpsc::UnboundedSender; use color_eyre::Result; use subxt::{ - ext::sp_core::crypto::{AccountId32, Ss58Codec, Ss58AddressFormat}, + backend::rpc::RpcClient, + client::OnlineClient, + config::substrate::DigestItem, + ext::sp_core::{ + crypto::{AccountId32, Ss58AddressFormat, Ss58Codec}, + Pair as PairT, + sr25519::Pair, + }, + rpc_params, + tx::{PairSigner, TxProgress}, utils::H256, - backend::rpc::RpcClient, - client::OnlineClient, - config::substrate::DigestItem, - rpc_params, }; + use crate::{ action::Action, casper_network::{ self, runtime_types::sp_consensus_slots, }, - types::{SystemAccount, EraInfo}, + types::{SystemAccount, EraInfo, ActionLevel}, CasperAccountId, CasperConfig }; @@ -147,7 +153,6 @@ pub async fn get_existential_deposit( Ok(()) } - pub async fn get_balance( action_tx: &UnboundedSender, api: &OnlineClient, @@ -178,3 +183,39 @@ pub async fn get_balance( action_tx.send(Action::BalanceResponse(*account_id, balance))?; Ok(()) } + +pub async fn transfer_balance( + action_tx: &UnboundedSender, + api: &OnlineClient, + sender: &[u8; 32], + receiver: &[u8; 32], + amount: &u128, +) -> Result>> { + let receiver_id = subxt::utils::MultiAddress::Id( + subxt::utils::AccountId32::from(*receiver) + ); + + let transfer_tx = casper_network::tx() + .balances() + .transfer_allow_death(receiver_id, *amount); + + let pair = Pair::from_seed(sender); + let signer = PairSigner::::new(pair); + + match api + .tx() + .sign_and_submit_then_watch_default(&transfer_tx, &signer) + .await { + Ok(tx_progress) => { + action_tx.send(Action::WalletLog( + format!("transfer transaction {} sent", tx_progress.extrinsic_hash()), + ActionLevel::Info))?; + Ok(tx_progress) + }, + Err(err) => { + action_tx.send(Action::WalletLog( + format!("error during transfer: {err}"), ActionLevel::Error))?; + Err(err.into()) + } + } +} diff --git a/src/network/subscriptions.rs b/src/network/subscriptions.rs index 05eb09a..4bd323d 100644 --- a/src/network/subscriptions.rs +++ b/src/network/subscriptions.rs @@ -48,9 +48,9 @@ impl FinalizedSubscription { self.action_tx.send(Action::FinalizedBlockInformation(block_hash, block_number))?; self.action_tx.send(Action::ExtrinsicsForBlock(block_number, extrinsic_details))?; - self.action_tx.send(Action::NewFinalizedHash(block_hash))?; self.action_tx.send(Action::NewFinalizedBlock(block_number))?; + self.network_tx.send(Action::NewFinalizedHash(block_hash))?; self.network_tx.send(Action::GetBlockAuthor(block_hash, block.header().digest.logs.clone()))?; } Ok(()) @@ -105,11 +105,11 @@ impl BestSubscription { self.action_tx.send(Action::BestBlockInformation(block_hash, block_number))?; self.action_tx.send(Action::ExtrinsicsForBlock(block_number, extrinsic_details))?; - self.action_tx.send(Action::NewBestHash(block_hash))?; self.action_tx.send(Action::BestBlockUpdated(block_number))?; self.action_tx.send(Action::NewBestBlock(block_number))?; self.action_tx.send(Action::ExtrinsicsLength(block_number, extrinsics_length))?; + self.network_tx.send(Action::NewBestHash(block_hash))?; self.network_tx.send(Action::GetBlockAuthor(block_hash, block.header().digest.logs.clone()))?; self.network_tx.send(Action::GetActiveEra)?; self.network_tx.send(Action::GetEpochProgress)?;