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"
[dependencies]
clap = { version = "4", features = ["derive"] }
clap = { version = "4", features = ["derive", "env"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

View file

@ -1,25 +1,56 @@
use anyhow::Result;
use clap::Subcommand;
use serde_json::json;
use crate::rpc::Client;
#[derive(Subcommand)]
pub enum NodeCmd {
/// Show chain info
/// Show chain info and latest block
Info,
/// Register as validator
Stake { #[arg(long)] amount: u64 },
/// Unstake from validator set
Unstake { #[arg(long)] amount: u64 },
/// Show a specific block by height
Block {
height: 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<()> {
match cmd {
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)?);
}
NodeCmd::Stake { amount } => println!("TODO: stake {amount} NUT"),
NodeCmd::Unstake { amount } => println!("TODO: unstake {amount} NUT"),
NodeCmd::Block { height } => {
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(())
}

View file

@ -1,44 +1,68 @@
use anyhow::Result;
use clap::Subcommand;
use serde_json::json;
use crate::rpc::Client;
#[derive(Subcommand)]
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 {
#[arg(long)] story_id: String,
#[arg(long)] parent_id: String,
#[arg(long)] ipfs_hash: String,
},
/// Register to vote on a node
/// Register to vote on a node (stub — Faz 1)
Register {
#[arg(long)] node_id: String,
},
/// Cast a vote
/// Cast a vote (stub — Faz 1)
Vote {
#[arg(long)] node_id: String,
#[arg(long)] approve: bool,
},
/// Show story DAG
Show {
#[arg(long)] story_id: String,
},
}
pub async fn run(cmd: StoryCmd, rpc: &Client) -> Result<()> {
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 } => {
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 } => {
println!("TODO: register vote for node={node_id}");
println!("TODO: register vote for node={node_id} (Faz 1)");
}
StoryCmd::Vote { node_id, approve } => {
println!("TODO: vote node={node_id} approve={approve}");
}
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)?);
println!("TODO: vote node={node_id} approve={approve} (Faz 1)");
}
}
Ok(())

View file

@ -9,12 +9,12 @@ pub struct Client {
impl Client {
pub fn new(endpoint: &str) -> Self {
Self {
endpoint: endpoint.to_string(),
endpoint: format!("{}/rpc", endpoint.trim_end_matches("/rpc")),
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!({
"jsonrpc": "2.0",
"method": method,
@ -29,7 +29,7 @@ impl Client {
.json()
.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);
}
Ok(resp["result"].clone())