feat(cli): implement node block/account cmds; fix params to positional array

This commit is contained in:
Mukan Erkin TÖRÜK 2026-04-24 11:21:42 +03:00
parent f009e50ee8
commit e0f7fe6bd6
5 changed files with 2368 additions and 26 deletions

2287
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@ name = "nu"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive", "env"] }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"

View file

@ -1,25 +1,56 @@
use anyhow::Result; use anyhow::Result;
use clap::Subcommand; use clap::Subcommand;
use serde_json::json;
use crate::rpc::Client; use crate::rpc::Client;
#[derive(Subcommand)] #[derive(Subcommand)]
pub enum NodeCmd { pub enum NodeCmd {
/// Show chain info /// Show chain info and latest block
Info, Info,
/// Register as validator /// Show a specific block by height
Stake { #[arg(long)] amount: u64 }, Block {
/// Unstake from validator set height: u64,
Unstake { #[arg(long)] amount: u64 }, },
/// Show account state
Account {
address: String,
},
/// Register as validator (stub — Faz 1)
Stake {
#[arg(long)] amount: u64,
},
/// Unstake from validator set (stub — Faz 1)
Unstake {
#[arg(long)] amount: u64,
},
} }
pub async fn run(cmd: NodeCmd, rpc: &Client) -> Result<()> { pub async fn run(cmd: NodeCmd, rpc: &Client) -> Result<()> {
match cmd { match cmd {
NodeCmd::Info => { NodeCmd::Info => {
let info = rpc.call("nu_chainInfo", serde_json::json!({})).await?; let info = rpc.call("nu_chainInfo", vec![]).await?;
println!("{}", serde_json::to_string_pretty(&info)?); println!("{}", serde_json::to_string_pretty(&info)?);
} }
NodeCmd::Stake { amount } => println!("TODO: stake {amount} NUT"), NodeCmd::Block { height } => {
NodeCmd::Unstake { amount } => println!("TODO: unstake {amount} NUT"), let block = rpc.call("nu_getBlock", vec![json!(height)]).await?;
if block.is_null() {
println!("Block {height} not found");
} else {
println!("{}", serde_json::to_string_pretty(&block)?);
}
}
NodeCmd::Account { address } => {
let account = rpc.call("nu_getAccount", vec![json!(address)]).await?;
let balance_shell: u64 = account["balance"].as_u64().unwrap_or(0);
let staked_shell: u64 = account["staked"].as_u64().unwrap_or(0);
println!("Address : {address}");
println!("Balance : {} Shell ({:.4} NU)", balance_shell, balance_shell as f64 / 100_000.0);
println!("Staked : {} Shell ({:.4} NU)", staked_shell, staked_shell as f64 / 100_000.0);
println!("Nonce : {}", account["nonce"].as_u64().unwrap_or(0));
println!("PoN : {}", account["pon_score"].as_f64().unwrap_or(1.0));
}
NodeCmd::Stake { amount } => println!("TODO: stake {amount} Shell (Faz 1)"),
NodeCmd::Unstake { amount } => println!("TODO: unstake {amount} Shell (Faz 1)"),
} }
Ok(()) Ok(())
} }

View file

@ -1,44 +1,68 @@
use anyhow::Result; use anyhow::Result;
use clap::Subcommand; use clap::Subcommand;
use serde_json::json;
use crate::rpc::Client; use crate::rpc::Client;
#[derive(Subcommand)] #[derive(Subcommand)]
pub enum StoryCmd { pub enum StoryCmd {
/// Submit a new story node /// Show story DAG with all nodes
Show {
#[arg(long)] story_id: String,
},
/// Show a specific node
Node {
#[arg(long)] node_id: String,
},
/// List all stories
List {
#[arg(long, default_value = "1")] page: u64,
#[arg(long, default_value = "10")] per_page: u64,
},
/// List nodes currently open for voting
Pending,
/// Submit a new story node (stub — requires wallet integration)
Submit { Submit {
#[arg(long)] story_id: String, #[arg(long)] story_id: String,
#[arg(long)] parent_id: String, #[arg(long)] parent_id: String,
#[arg(long)] ipfs_hash: String, #[arg(long)] ipfs_hash: String,
}, },
/// Register to vote on a node /// Register to vote on a node (stub — Faz 1)
Register { Register {
#[arg(long)] node_id: String, #[arg(long)] node_id: String,
}, },
/// Cast a vote /// Cast a vote (stub — Faz 1)
Vote { Vote {
#[arg(long)] node_id: String, #[arg(long)] node_id: String,
#[arg(long)] approve: bool, #[arg(long)] approve: bool,
}, },
/// Show story DAG
Show {
#[arg(long)] story_id: String,
},
} }
pub async fn run(cmd: StoryCmd, rpc: &Client) -> Result<()> { pub async fn run(cmd: StoryCmd, rpc: &Client) -> Result<()> {
match cmd { match cmd {
StoryCmd::Show { story_id } => {
let result = rpc.call("nu_getStory", vec![json!(story_id)]).await?;
println!("{}", serde_json::to_string_pretty(&result)?);
}
StoryCmd::Node { node_id } => {
let result = rpc.call("nu_getNode", vec![json!(node_id)]).await?;
println!("{}", serde_json::to_string_pretty(&result)?);
}
StoryCmd::List { page, per_page } => {
let result = rpc.call("nu_listStories", vec![json!(page), json!(per_page)]).await?;
println!("{}", serde_json::to_string_pretty(&result)?);
}
StoryCmd::Pending => {
let result = rpc.call("nu_listPendingVotes", vec![]).await?;
println!("{}", serde_json::to_string_pretty(&result)?);
}
StoryCmd::Submit { story_id, parent_id, ipfs_hash } => { StoryCmd::Submit { story_id, parent_id, ipfs_hash } => {
println!("TODO: submit node story={story_id} parent={parent_id} hash={ipfs_hash}"); println!("TODO: submit node story={story_id} parent={parent_id} hash={ipfs_hash} (requires wallet — Faz 1)");
} }
StoryCmd::Register { node_id } => { StoryCmd::Register { node_id } => {
println!("TODO: register vote for node={node_id}"); println!("TODO: register vote for node={node_id} (Faz 1)");
} }
StoryCmd::Vote { node_id, approve } => { StoryCmd::Vote { node_id, approve } => {
println!("TODO: vote node={node_id} approve={approve}"); println!("TODO: vote node={node_id} approve={approve} (Faz 1)");
}
StoryCmd::Show { story_id } => {
let result = rpc.call("nu_getStory", serde_json::json!({ "story_id": story_id })).await?;
println!("{}", serde_json::to_string_pretty(&result)?);
} }
} }
Ok(()) Ok(())

View file

@ -9,12 +9,12 @@ pub struct Client {
impl Client { impl Client {
pub fn new(endpoint: &str) -> Self { pub fn new(endpoint: &str) -> Self {
Self { Self {
endpoint: endpoint.to_string(), endpoint: format!("{}/rpc", endpoint.trim_end_matches("/rpc")),
http: reqwest::Client::new(), http: reqwest::Client::new(),
} }
} }
pub async fn call(&self, method: &str, params: Value) -> Result<Value> { pub async fn call(&self, method: &str, params: Vec<Value>) -> Result<Value> {
let body = json!({ let body = json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": method, "method": method,
@ -29,7 +29,7 @@ impl Client {
.json() .json()
.await?; .await?;
if let Some(err) = resp.get("error") { if let Some(err) = resp.get("error").filter(|e| !e.is_null()) {
anyhow::bail!("RPC error: {}", err); anyhow::bail!("RPC error: {}", err);
} }
Ok(resp["result"].clone()) Ok(resp["result"].clone())