Merge branch 'main' into schouhhy/expand-readme

This commit is contained in:
Sergio Chouhy 2025-11-20 00:14:02 -03:00
commit 1214bf6e7e
9 changed files with 1374 additions and 574 deletions

View File

@ -138,16 +138,14 @@ If everything went well you should see an output similar to this:
# Try the Wallet CLI
## Install
This repository includes a CLI for interacting with the Nescience sequencer. To install it, run the following command from the root of the repository:
```bash
cargo install --path wallet --force
```
Before using the CLI, set the environment variable `NSSA_WALLET_HOME_DIR` to the directory containing the wallet configuration file. A sample configuration is available at `integration_tests/configs/debug/wallet/`. To use it, run:
```bash
export NSSA_WALLET_HOME_DIR=$(pwd)/integration_tests/configs/debug/wallet/
```
Run `wallet help` to check everything went well.
## Tutorial
@ -519,7 +517,6 @@ wallet token new \
After it succeeds, we can check their values
```bash
wallet account get --addr Public/GQ3C8rbprTtQUCvkuVBRu3v9wvUvjafCMFqoSPvTEVii

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,7 @@ use wallet::{
Command, SubcommandReturnValue, WalletCore,
cli::{
account::{AccountSubcommand, NewSubcommand},
config::ConfigSubcommand,
native_token_transfer_program::AuthTransferSubcommand,
pinata_program::PinataProgramAgnosticSubcommand,
token_program::TokenProgramAgnosticSubcommand,
@ -1188,11 +1189,6 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
.await
.unwrap();
let new_commitment1 = wallet_storage
.get_private_account_commitment(&from)
.unwrap();
assert_eq!(tx.message.new_commitments[0], new_commitment1);
assert_eq!(tx.message.new_commitments.len(), 2);
for commitment in tx.message.new_commitments.into_iter() {
assert!(verify_commitment_is_in_state(commitment, &seq_client).await);
@ -1589,6 +1585,34 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
info!("Success!");
}
#[nssa_integration_test]
pub async fn test_modify_config_fields() {
info!("########## test_modify_config_fields ##########");
let wallet_config = fetch_config().await.unwrap();
let old_seq_poll_retry_delay_millis = wallet_config.seq_poll_retry_delay_millis;
//Change config field
let command = Command::Config(ConfigSubcommand::Set {
key: "seq_poll_retry_delay_millis".to_string(),
value: "1000".to_string(),
});
wallet::execute_subcommand(command).await.unwrap();
let wallet_config = fetch_config().await.unwrap();
assert_eq!(wallet_config.seq_poll_retry_delay_millis, 1000);
//Return how it was at the beginning
let command = Command::Config(ConfigSubcommand::Set {
key: "seq_poll_retry_delay_millis".to_string(),
value: old_seq_poll_retry_delay_millis.to_string(),
});
wallet::execute_subcommand(command).await.unwrap();
info!("Success!");
}
println!("{function_map:#?}");
function_map

View File

@ -71,8 +71,6 @@ mod tests {
use crate::config::InitialAccountData;
use super::*;
use std::path::PathBuf;
use tempfile::tempdir;
fn create_initial_accounts() -> Vec<InitialAccountData> {
let initial_acc1 = serde_json::from_str(
@ -166,9 +164,8 @@ mod tests {
initial_accounts
}
fn create_sample_wallet_config(home: PathBuf) -> WalletConfig {
fn create_sample_wallet_config() -> WalletConfig {
WalletConfig {
home,
override_rust_log: None,
sequencer_addr: "http://127.0.0.1".to_string(),
seq_poll_timeout_millis: 12000,
@ -181,10 +178,7 @@ mod tests {
#[test]
fn test_new_initializes_correctly() {
let temp_dir = tempdir().unwrap();
let path = temp_dir.path();
let config = create_sample_wallet_config(path.to_path_buf());
let config = create_sample_wallet_config();
let _ = WalletChainStore::new(config.clone()).unwrap();
}

151
wallet/src/cli/config.rs Normal file
View File

@ -0,0 +1,151 @@
use anyhow::Result;
use clap::Subcommand;
use crate::{SubcommandReturnValue, WalletCore, cli::WalletSubcommand};
///Represents generic config CLI subcommand
#[derive(Subcommand, Debug, Clone)]
pub enum ConfigSubcommand {
/// Command to explicitly setup config and storage
///
/// Does nothing in case if both already present
Setup {},
/// Getter of config fields
Get { key: String },
/// Setter of config fields
Set { key: String, value: String },
/// Prints description of corresponding field
Description { key: String },
}
impl WalletSubcommand for ConfigSubcommand {
async fn handle_subcommand(
self,
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
ConfigSubcommand::Setup {} => {
let path = wallet_core.store_persistent_data().await?;
println!("Stored persistent accounts at {path:#?}");
}
ConfigSubcommand::Get { key } => match key.as_str() {
"all" => {
let config_str =
serde_json::to_string_pretty(&wallet_core.storage.wallet_config)?;
println!("{config_str}");
}
"override_rust_log" => {
if let Some(value) = &wallet_core.storage.wallet_config.override_rust_log {
println!("{value}");
} else {
println!("Not set");
}
}
"sequencer_addr" => {
println!("{}", wallet_core.storage.wallet_config.sequencer_addr);
}
"seq_poll_timeout_millis" => {
println!(
"{}",
wallet_core.storage.wallet_config.seq_poll_timeout_millis
);
}
"seq_poll_max_blocks" => {
println!("{}", wallet_core.storage.wallet_config.seq_poll_max_blocks);
}
"seq_poll_max_retries" => {
println!("{}", wallet_core.storage.wallet_config.seq_poll_max_retries);
}
"seq_poll_retry_delay_millis" => {
println!(
"{}",
wallet_core
.storage
.wallet_config
.seq_poll_retry_delay_millis
);
}
"initial_accounts" => {
println!("{:#?}", wallet_core.storage.wallet_config.initial_accounts);
}
_ => {
println!("Unknown field");
}
},
ConfigSubcommand::Set { key, value } => {
match key.as_str() {
"override_rust_log" => {
wallet_core.storage.wallet_config.override_rust_log = Some(value);
}
"sequencer_addr" => {
wallet_core.storage.wallet_config.sequencer_addr = value;
}
"seq_poll_timeout_millis" => {
wallet_core.storage.wallet_config.seq_poll_timeout_millis =
value.parse()?;
}
"seq_poll_max_blocks" => {
wallet_core.storage.wallet_config.seq_poll_max_blocks = value.parse()?;
}
"seq_poll_max_retries" => {
wallet_core.storage.wallet_config.seq_poll_max_retries = value.parse()?;
}
"seq_poll_retry_delay_millis" => {
wallet_core
.storage
.wallet_config
.seq_poll_retry_delay_millis = value.parse()?;
}
"initial_accounts" => {
anyhow::bail!("Setting this field from wallet is not supported");
}
_ => {
anyhow::bail!("Unknown field");
}
}
let path = wallet_core.store_config_changes().await?;
println!("Stored changed config at {path:#?}");
}
ConfigSubcommand::Description { key } => match key.as_str() {
"override_rust_log" => {
println!("Value of variable RUST_LOG to override, affects logging");
}
"sequencer_addr" => {
println!("HTTP V4 address of sequencer");
}
"seq_poll_timeout_millis" => {
println!(
"Sequencer client retry variable: how much time to wait between retries in milliseconds(can be zero)"
);
}
"seq_poll_max_blocks" => {
println!(
"Sequencer client polling variable: max number of blocks to poll in parallel"
);
}
"seq_poll_max_retries" => {
println!(
"Sequencer client retry variable: MAX number of retries before failing(can be zero)"
);
}
"seq_poll_retry_delay_millis" => {
println!(
"Sequencer client polling variable: how much time to wait in milliseconds between polling retries(can be zero)"
);
}
"initial_accounts" => {
println!("List of initial accounts' keys(both public and private)");
}
_ => {
println!("Unknown field");
}
},
}
Ok(SubcommandReturnValue::Empty)
}
}

View File

@ -4,6 +4,7 @@ use crate::{SubcommandReturnValue, WalletCore};
pub mod account;
pub mod chain;
pub mod config;
pub mod native_token_transfer_program;
pub mod pinata_program;
pub mod token_program;

View File

@ -1,6 +1,5 @@
use key_protocol::key_management::KeyChain;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitialAccountDataPublic {
@ -114,8 +113,6 @@ pub struct GasConfig {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WalletConfig {
///Home dir of sequencer storage
pub home: PathBuf,
///Override rust log (env var logging level)
pub override_rust_log: Option<String>,
///Sequencer URL
@ -131,3 +128,559 @@ pub struct WalletConfig {
///Initial accounts for wallet
pub initial_accounts: Vec<InitialAccountData>,
}
impl Default for WalletConfig {
fn default() -> Self {
Self {
override_rust_log: None,
sequencer_addr: "http://127.0.0.1:3040".to_string(),
seq_poll_timeout_millis: 12000,
seq_poll_max_blocks: 5,
seq_poll_max_retries: 5,
seq_poll_retry_delay_millis: 500,
initial_accounts: {
let init_acc_json = r#"
[
{
"Public": {
"address": "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy",
"pub_sign_key": [
16,
162,
106,
154,
236,
125,
52,
184,
35,
100,
238,
174,
69,
197,
41,
77,
187,
10,
118,
75,
0,
11,
148,
238,
185,
181,
133,
17,
220,
72,
124,
77
]
}
},
{
"Public": {
"address": "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw",
"pub_sign_key": [
113,
121,
64,
177,
204,
85,
229,
214,
178,
6,
109,
191,
29,
154,
63,
38,
242,
18,
244,
219,
8,
208,
35,
136,
23,
127,
207,
237,
216,
169,
190,
27
]
}
},
{
"Private": {
"address": "3oCG8gqdKLMegw4rRfyaMQvuPHpcASt7xwttsmnZLSkw",
"account": {
"program_owner": [
0,
0,
0,
0,
0,
0,
0,
0
],
"balance": 10000,
"data": [],
"nonce": 0
},
"key_chain": {
"secret_spending_key": [
251,
82,
235,
1,
146,
96,
30,
81,
162,
234,
33,
15,
123,
129,
116,
0,
84,
136,
176,
70,
190,
224,
161,
54,
134,
142,
154,
1,
18,
251,
242,
189
],
"private_key_holder": {
"nullifier_secret_key": [
29,
250,
10,
187,
35,
123,
180,
250,
246,
97,
216,
153,
44,
156,
16,
93,
241,
26,
174,
219,
72,
84,
34,
247,
112,
101,
217,
243,
189,
173,
75,
20
],
"incoming_viewing_secret_key": [
251,
201,
22,
154,
100,
165,
218,
108,
163,
190,
135,
91,
145,
84,
69,
241,
46,
117,
217,
110,
197,
248,
91,
193,
14,
104,
88,
103,
67,
153,
182,
158
],
"outgoing_viewing_secret_key": [
25,
67,
121,
76,
175,
100,
30,
198,
105,
123,
49,
169,
75,
178,
75,
210,
100,
143,
210,
243,
228,
243,
21,
18,
36,
84,
164,
186,
139,
113,
214,
12
]
},
"nullifer_public_key": [
63,
202,
178,
231,
183,
82,
237,
212,
216,
221,
215,
255,
153,
101,
177,
161,
254,
210,
128,
122,
54,
190,
230,
151,
183,
64,
225,
229,
113,
1,
228,
97
],
"incoming_viewing_public_key": [
3,
235,
139,
131,
237,
177,
122,
189,
6,
177,
167,
178,
202,
117,
246,
58,
28,
65,
132,
79,
220,
139,
119,
243,
187,
160,
212,
121,
61,
247,
116,
72,
205
]
}
}
},
{
"Private": {
"address": "AKTcXgJ1xoynta1Ec7y6Jso1z1JQtHqd7aPQ1h9er6xX",
"account": {
"program_owner": [
0,
0,
0,
0,
0,
0,
0,
0
],
"balance": 20000,
"data": [],
"nonce": 0
},
"key_chain": {
"secret_spending_key": [
238,
171,
241,
69,
111,
217,
85,
64,
19,
82,
18,
189,
32,
91,
78,
175,
107,
7,
109,
60,
52,
44,
243,
230,
72,
244,
192,
92,
137,
33,
118,
254
],
"private_key_holder": {
"nullifier_secret_key": [
25,
211,
215,
119,
57,
223,
247,
37,
245,
144,
122,
29,
118,
245,
83,
228,
23,
9,
101,
120,
88,
33,
238,
207,
128,
61,
110,
2,
89,
62,
164,
13
],
"incoming_viewing_secret_key": [
193,
181,
14,
196,
142,
84,
15,
65,
128,
101,
70,
196,
241,
47,
130,
221,
23,
146,
161,
237,
221,
40,
19,
126,
59,
15,
169,
236,
25,
105,
104,
231
],
"outgoing_viewing_secret_key": [
20,
170,
220,
108,
41,
23,
155,
217,
247,
190,
175,
168,
247,
34,
105,
134,
114,
74,
104,
91,
211,
62,
126,
13,
130,
100,
241,
214,
250,
236,
38,
150
]
},
"nullifer_public_key": [
192,
251,
166,
243,
167,
236,
84,
249,
35,
136,
130,
172,
219,
225,
161,
139,
229,
89,
243,
125,
194,
213,
209,
30,
23,
174,
100,
244,
124,
74,
140,
47
],
"incoming_viewing_public_key": [
2,
181,
98,
93,
216,
241,
241,
110,
58,
198,
119,
174,
250,
184,
1,
204,
200,
173,
44,
238,
37,
247,
170,
156,
100,
254,
116,
242,
28,
183,
187,
77,
255
]
}
}
}
]
"#;
serde_json::from_str(init_acc_json).unwrap()
},
}
}
}

View File

@ -2,7 +2,7 @@ use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
use nssa_core::account::Nonce;
use rand::{RngCore, rngs::OsRng};
use std::{path::PathBuf, str::FromStr};
use tokio::io::AsyncReadExt;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use anyhow::Result;
use key_protocol::key_protocol_core::NSSAUserData;
@ -17,19 +17,78 @@ use crate::{
};
/// Get home dir for wallet. Env var `NSSA_WALLET_HOME_DIR` must be set before execution to succeed.
pub fn get_home() -> Result<PathBuf> {
pub fn get_home_nssa_var() -> Result<PathBuf> {
Ok(PathBuf::from_str(&std::env::var(HOME_DIR_ENV_VAR)?)?)
}
/// Fetch config from `NSSA_WALLET_HOME_DIR`
pub async fn fetch_config() -> Result<WalletConfig> {
let config_home = get_home()?;
let config_contents = tokio::fs::read(config_home.join("wallet_config.json")).await?;
Ok(serde_json::from_slice(&config_contents)?)
/// Get home dir for wallet. Env var `HOME` must be set before execution to succeed.
pub fn get_home_default_path() -> Result<PathBuf> {
std::env::home_dir()
.map(|path| path.join(".nssa").join("wallet"))
.ok_or(anyhow::anyhow!("Failed to get HOME"))
}
/// Fetch data stored at `NSSA_WALLET_HOME_DIR/storage.json`
/// Get home dir for wallet.
pub fn get_home() -> Result<PathBuf> {
if let Ok(home) = get_home_nssa_var() {
Ok(home)
} else {
get_home_default_path()
}
}
/// Fetch config from default home
pub async fn fetch_config() -> Result<WalletConfig> {
let config_home = get_home()?;
let mut config_needs_setup = false;
let config = match tokio::fs::OpenOptions::new()
.read(true)
.open(config_home.join("wallet_config.json"))
.await
{
Ok(mut file) => {
let mut config_contents = vec![];
file.read_to_end(&mut config_contents).await?;
serde_json::from_slice(&config_contents)?
}
Err(err) => match err.kind() {
std::io::ErrorKind::NotFound => {
config_needs_setup = true;
println!("Config not found, setting up default config");
WalletConfig::default()
}
_ => anyhow::bail!("IO error {err:#?}"),
},
};
if config_needs_setup {
tokio::fs::create_dir_all(&config_home).await?;
println!("Created configs dir at path {config_home:#?}");
let mut file = tokio::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(config_home.join("wallet_config.json"))
.await?;
let default_config_serialized =
serde_json::to_vec_pretty(&WalletConfig::default()).unwrap();
file.write_all(&default_config_serialized).await?;
println!("Configs setted up");
}
Ok(config)
}
/// Fetch data stored at home
///
/// If file not present, it is considered as empty list of persistent accounts
pub async fn fetch_persistent_storage() -> Result<PersistentStorage> {

View File

@ -23,7 +23,7 @@ use tokio::io::AsyncWriteExt;
use crate::{
cli::{
WalletSubcommand, account::AccountSubcommand, chain::ChainSubcommand,
native_token_transfer_program::AuthTransferSubcommand,
config::ConfigSubcommand, native_token_transfer_program::AuthTransferSubcommand,
pinata_program::PinataProgramAgnosticSubcommand,
token_program::TokenProgramAgnosticSubcommand,
},
@ -93,6 +93,20 @@ impl WalletCore {
Ok(storage_path)
}
///Store persistent data at home
pub async fn store_config_changes(&self) -> Result<PathBuf> {
let home = get_home()?;
let config_path = home.join("wallet_config.json");
let config = serde_json::to_vec_pretty(&self.storage.wallet_config)?;
let mut config_file = tokio::fs::File::create(config_path.as_path()).await?;
config_file.write_all(&config).await?;
info!("Stored data at {config_path:#?}");
Ok(config_path)
}
pub fn create_new_account_public(&mut self) -> Address {
self.storage
.user_data
@ -216,6 +230,9 @@ pub enum Command {
/// Check the wallet can connect to the node and builtin local programs
/// match the remote versions
CheckHealth {},
/// Command to setup config, get and set config fields
#[command(subcommand)]
Config(ConfigSubcommand),
}
///To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config
@ -300,6 +317,11 @@ pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValu
Command::Token(token_subcommand) => {
token_subcommand.handle_subcommand(&mut wallet_core).await?
}
Command::Config(config_subcommand) => {
config_subcommand
.handle_subcommand(&mut wallet_core)
.await?
}
};
Ok(subcommand_ret)