Skip to content

Commit

Permalink
chore: add new features (#300)
Browse files Browse the repository at this point in the history
* fix: sender error after account switch (#297)

* Sender error after account switch

* fix: typo

* Implement account note feature for enhanced account management (#298) (#299)

* Changes info icon on the wallet view

* Updates Cargo.lock file

* Fixes balance info for different locales than en-US

* Adds persistence to locale preference

* Changes app name from Carpe to carpe

* Inits account events on account switch

* Fixes displaying events of an account off chain

* Adds optimistic account switch

Clean up code

* Unsubscribes Miner subscriptions onDestroy

* Optimizes Miner components subscriptions

* UX - Adds tower state skeleton

* Fixes wallet components memory leak

Fixes coin print according to language selected

Clean up code

* Adds coin transfer - WIP

* Do not notify initial account selection

* Transfers coins

* Coin transfer adjustments

* Translates onboard and transfer buttons

* Fixes modal close on success

* Fixes onboard modal close onSucess

* Fixes events pagination vertical alignment

* Fixes check sender vs receiver addresses

* Adds sent payments to be displayed on the events tab

* Adds wallet skeleton

Update libra branch version

* Removes viewport from components to avoid vertical scroll bar

* Adds lang missing keys

* Adds missing language keys

* Fixes printCoins for Chinese

* Fix rust code getting best fullnode url

* Wait for account to be loaded before showing scanning for fullnodes

* Hides make whole navbar link

* removes events tab

* adds account note feature - WIP

* improves account note feature
saves accounts list sort and order preferences
optimizes svelte components load

* hides total balance and unlocked when there is only one account
avoids wrap line on table header

* removes console.log

* clean warnings

* assigns note to account on switch signingAccount

---------

Co-authored-by: BBK <chnqbj@gmail.com>
Co-authored-by: soaresa <10797037+soaresa@users.noreply.github.com>
  • Loading branch information
3 people authored May 15, 2024
1 parent 752bee2 commit b0304ec
Show file tree
Hide file tree
Showing 28 changed files with 573 additions and 192 deletions.
3 changes: 2 additions & 1 deletion src-tauri/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
pub(crate) mod app_version;
pub(crate) mod mining;
// pub(crate) mod mining;
pub(crate) mod networks;
pub(crate) mod preferences;
pub(crate) mod query;
pub(crate) mod tx;
pub(crate) mod wallets;
pub(crate) mod web_logs;
pub(crate) mod user_preferences;

//pub use app_version::*;
4 changes: 2 additions & 2 deletions src-tauri/src/commands/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ pub async fn coin_transfer(
};

let mut config = get_cfg()?;
inject_private_key_to_cfg(&mut config)?;
let mut sender = Sender::from_app_cfg(&config, None, legacy).await?;
inject_private_key_to_cfg(&mut config, _sender)?;
let mut sender = Sender::from_app_cfg(&config, Some(_sender.to_string()), legacy).await?;
sender
.transfer(receiver_account, amount as f64, false)
.await?;
Expand Down
61 changes: 61 additions & 0 deletions src-tauri/src/commands/user_preferences.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use crate::carpe_error::CarpeError;
use serde::{Deserialize, Serialize};
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::path::{PathBuf};
use crate::configs::default_config_path;

#[derive(Serialize, Deserialize)]
pub struct UserPreferences {
accounts_list_sort_column: Option<String>,
accounts_list_sort_order: Option<String>,
}

// Utility function to retrieve the full path to the preferences file
fn get_preferences_path() -> Result<PathBuf, CarpeError> {
let app_dir_path = default_config_path(); // Assuming this returns a PathBuf or Path

// Check if the path exists, if not, return an error
if !app_dir_path.exists() {
return Err(CarpeError::misc("App directory not found"));
}

Ok(app_dir_path.join("user_preferences.json"))
}

#[tauri::command(async)]
pub async fn get_user_preferences() -> Result<UserPreferences, CarpeError> {
let file_path = get_preferences_path()?;
match File::open(&file_path) {
Ok(mut file) => {
let mut contents = String::new();
if let Err(e) = file.read_to_string(&mut contents) {
return Err(CarpeError::misc(&format!("Failed to read from preferences file: {}", e)));
}
serde_json::from_str(&contents).map_err(|e| CarpeError::misc(&format!("Failed to parse preferences: {}", e)))
},
Err(e) => Err(CarpeError::misc(&format!("Failed to open preferences file: {}", e))),
}
}

#[tauri::command(async)]
pub async fn set_accounts_list_preference(sort_column: String, sort_order: String) -> Result<(), CarpeError> {
let file_path = get_preferences_path()?;
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&file_path)
.map_err(|e| CarpeError::misc(&format!("Failed to open preferences file for writing: {}", e)))?;

let preferences = UserPreferences {
accounts_list_sort_column: Some(sort_column),
accounts_list_sort_order: Some(sort_order),
};

let serialized_data = serde_json::to_string_pretty(&preferences)
.map_err(|e| CarpeError::misc(&format!("Failed to serialize preferences: {}", e)))?;

file.write_all(serialized_data.as_bytes())
.map_err(|e| CarpeError::misc(&format!("Failed to write preferences file: {}", e)))
}
127 changes: 115 additions & 12 deletions src-tauri/src/commands/wallets.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
carpe_error::CarpeError,
commands::query,
configs::{self, default_legacy_account_path, get_cfg, get_client},
configs::{self, default_legacy_account_path, get_cfg, get_client, default_config_path},
configs_profile,
key_manager::{self, get_private_key, inject_private_key_to_cfg},
};
Expand All @@ -17,7 +17,10 @@ use libra_types::{
use libra_wallet::account_keys::{self, KeyChain};
use serde::{Deserialize, Serialize};
use std::fs::{self, File};
use std::io::prelude::*;
use std::io::{Write, prelude::*};
use std::fs::OpenOptions;
use std::path::{PathBuf, Path};
use serde_json;

#[derive(serde::Deserialize, serde::Serialize, Debug)]
pub struct NewKeygen {
Expand Down Expand Up @@ -49,6 +52,7 @@ pub struct CarpeProfile {
on_chain: bool,
balance: SlowWalletBalance,
locale: Option<String>, // TODO: refactor, tauri now offers locale of the OS
note: Option<String>,
}

impl From<&Profile> for CarpeProfile {
Expand All @@ -63,6 +67,7 @@ impl From<&Profile> for CarpeProfile {
total: core_profile.balance.total,
}, // TODO: refactor upstream to have Clone
locale: core_profile.locale.clone(),
note: None,
}
}
}
Expand Down Expand Up @@ -123,6 +128,12 @@ pub async fn init_from_private_key(
Ok(core_profile.into())
}

#[derive(Serialize, Deserialize)]
struct Note {
account: String,
note: String,
}

/// read all accounts from profile
#[tauri::command(async)]
pub fn get_all_accounts() -> Result<Vec<CarpeProfile>, CarpeError> {
Expand All @@ -131,6 +142,93 @@ pub fn get_all_accounts() -> Result<Vec<CarpeProfile>, CarpeError> {
Ok(mapped)
}

/// read all accounts from profile plus notes
#[tauri::command]
pub fn get_all_accounts_with_notes() -> Result<Vec<CarpeProfile>, CarpeError> {
let mut accounts = get_all_accounts()?;
let _ = assign_notes_to_accounts(&mut accounts);
Ok(accounts)
}

fn notes_file_path() -> PathBuf {
let app_dir_path = default_config_path(); // Assuming this returns a PathBuf or Path
return app_dir_path.join("account_notes.json");
}


fn read_notes() -> Result<Vec<Note>, CarpeError> {
let file_path = notes_file_path();

// Check if the file exists before attempting to open it
if !Path::new(&file_path).exists() {
return Ok(vec![]); // Return an empty vector if the file does not exist
}

let mut file = match File::open(&file_path) {
Ok(f) => f,
Err(_e) => return Err(CarpeError::misc("Failed to open notes file")),
};

let mut contents = String::new();
if let Err(_e) = file.read_to_string(&mut contents) {
return Err(CarpeError::misc("Failed to read from notes file"));
}

match serde_json::from_str(&contents) {
Ok(notes) => Ok(notes),
Err(_e) => Err(CarpeError::misc("Failed to parse notes JSON")),
}
}

fn assign_notes_to_accounts(accounts: &mut Vec<CarpeProfile>) -> Result<(), CarpeError> {
let notes = read_notes()?;
for account in accounts.iter_mut() {
let note_option = notes.iter()
.find(|note| note.account == account.account.to_string().to_uppercase())
.map(|note| note.note.clone());
account.note = note_option;
}
Ok(())
}
#[tauri::command]
pub fn associate_note_with_account(account: String, note: String) -> Result<(), CarpeError> {
let file_path = notes_file_path();
let mut notes = read_notes().map_err(|_e| CarpeError::misc("Failed to read notes"))?;
let address = account.to_uppercase();

// Check if the account already exists and update the note if it does
let mut found = false;
for account_note in notes.iter_mut() {
if account_note.account == address {
account_note.note = note.clone();
found = true;
break;
}
}

// If the account does not exist, add a new note
if !found {
notes.push(Note { account: address, note });
}

let notes_json = serde_json::to_string(&notes)
.map_err(|_e| CarpeError::misc("Failed to serialize notes"))?;

let mut file = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(&file_path)
.map_err(|_e| CarpeError::misc("Failed to open file for writing"))?;

file.write_all(notes_json.as_bytes())
.map_err(|_e| CarpeError::misc("Failed to write to file"))?;

Ok(())
}



/// read all accounts from profile
#[tauri::command(async)]
pub fn get_default_profile() -> Result<CarpeProfile, CarpeError> {
Expand All @@ -150,23 +248,22 @@ pub async fn refresh_accounts() -> Result<Vec<CarpeProfile>, CarpeError> {
map_get_balance(&mut app_cfg.user_profiles).await?;
app_cfg.save_file()?;

let mapped: Vec<CarpeProfile> = app_cfg.user_profiles.iter().map(|p| p.into()).collect();
let mut mapped: Vec<CarpeProfile> = app_cfg.user_profiles.iter().map(|p| p.into()).collect();
let _ = assign_notes_to_accounts(&mut mapped);
Ok(mapped)
}

#[tauri::command(async)]
/// check if this account is a slow wallet
pub async fn is_slow(account: AccountAddress) -> anyhow::Result<bool, CarpeError> {
let c = get_client()?;
println!("is slow");
let b = c
.view_ext(
"0x1::slow_wallet::is_slow",
None,
Some(account.to_hex_literal()),
)
.await?;
dbg!(&b);
match b.as_array().context("no bool found")?[0].as_bool() {
Some(b) => Ok(b),
None => Ok(false),
Expand Down Expand Up @@ -210,17 +307,23 @@ pub async fn get_originating_address(
}
}

/// Switch tx profiles, change 0L.toml to use selected account
/// Switch tx profiles, change libra.yaml to use selected account
#[tauri::command(async)]
// IMPORTANT: don't return the profile, since it has keys
pub async fn switch_profile(account: AccountAddress) -> Result<CarpeProfile, CarpeError> {
let mut app_cfg = get_cfg()?;
let p = app_cfg.get_profile(Some(account.to_string()))?;
app_cfg.workspace.set_default(p.nickname.clone());
app_cfg.save_file()?;
// TODO: gross, fix upstream `app_cfg.rs` to prevent the borrow issues here
let profile = app_cfg.get_profile(Some(account.to_string()))?;
Ok(profile.into())

// TODO: gross, fix upstream `app_cfg.rs` to prevent the borrow issues here
let mut profile = app_cfg.get_profile(Some(account.to_string()))?;

// Assign account note
let mut profiles: Vec<CarpeProfile> = vec![profile.into()];
assign_notes_to_accounts(&mut profiles)?;

Ok(profiles.into_iter().next().unwrap().into())
}

// remove all accounts which are being tracked.
Expand Down Expand Up @@ -266,11 +369,11 @@ async fn test_fetch_originating() {
}

#[tauri::command(async)]
pub async fn set_slow_wallet(legacy: bool) -> Result<(), CarpeError> {
pub async fn set_slow_wallet(legacy: bool, _sender: AccountAddress) -> Result<(), CarpeError> {
// NOTE: unsure Serde was catching all cases check serialization
let mut config = get_cfg()?;
inject_private_key_to_cfg(&mut config)?;
let mut sender = Sender::from_app_cfg(&config, None, legacy).await?;
inject_private_key_to_cfg(&mut config, _sender)?;
let mut sender = Sender::from_app_cfg(&config, Some(_sender.to_string()), legacy).await?;

let t = SetSlowTx {};
t.run(&mut sender).await?;
Expand Down
7 changes: 5 additions & 2 deletions src-tauri/src/key_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,12 @@ pub fn get_keypair(
/// insert the public key into the AppCfg temporarily so that we don't need
/// to prompt user for mnemonic.
// NOTE to future devs: DANGER: make sure this is never called in a flow that uses save_file(). The upstream prevents the key from serializing, but it should be guarded here as well.
pub fn inject_private_key_to_cfg(app_cfg_mut: &mut AppCfg) -> anyhow::Result<(), CarpeError> {
pub fn inject_private_key_to_cfg(
app_cfg_mut: &mut AppCfg,
account: AccountAddress,
) -> anyhow::Result<(), CarpeError> {
// gets the default profile
let profile = app_cfg_mut.get_profile_mut(None)?;
let profile = app_cfg_mut.get_profile_mut(Some(account.to_string()))?;
let pri_key = get_private_key(&profile.account).map_err(|e| CarpeError {
category: ErrorCat::Configs,
uid: E_KEY_NOT_REGISTERED,
Expand Down
22 changes: 14 additions & 8 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ async fn main() {
commands::wallets::get_default_profile,
commands::wallets::refresh_accounts,
commands::wallets::get_all_accounts,
commands::wallets::get_all_accounts_with_notes,
commands::wallets::associate_note_with_account,
commands::wallets::keygen,
commands::wallets::init_from_mnem,
commands::wallets::init_from_private_key,
Expand All @@ -114,14 +116,14 @@ async fn main() {
commands::tx::coin_transfer,
// claim_make_whole,
//////// Tower ////////
commands::query::get_onchain_tower_state,
commands::mining::miner_once,
commands::mining::start_backlog_sender_listener,
commands::mining::get_local_height,
commands::mining::get_epoch_rules,
commands::mining::submit_backlog,
commands::mining::get_last_local_proof,
commands::mining::debug_highest_proof_path,
// commands::query::get_onchain_tower_state,
// commands::mining::miner_once,
// commands::mining::start_backlog_sender_listener,
// commands::mining::get_local_height,
// commands::mining::get_epoch_rules,
// commands::mining::submit_backlog,
// commands::mining::get_last_local_proof,
// commands::mining::debug_highest_proof_path,
// submit_proof_zero,

//////// Preferences ////////
Expand All @@ -135,6 +137,10 @@ async fn main() {
commands::preferences::set_preferences_locale,
commands::preferences::get_miner_txs_cost,
commands::preferences::set_miner_txs_cost,

//////// User Preferences ////////
commands::user_preferences::get_user_preferences,
commands::user_preferences::set_accounts_list_preference,
///////// Debug ////////
commands::app_version::get_app_version,
commands::web_logs::log_this,
Expand Down
Loading

0 comments on commit b0304ec

Please sign in to comment.