synor/apps/cli/src/main.rs
Gulshan Yadav 97f42cb990 feat: add CLI commands, WebSocket channels, and API versioning
- Add CLI commands for DEX, IBC, ZK, and Compiler services
  - DEX: markets, orderbook, orders, liquidity pools
  - IBC: chains, transfers, packets, relayers
  - ZK: circuit compilation, proof generation (Groth16/PLONK/STARK)
  - Compiler: WASM compilation, ABI extraction, security scan

- Add WebSocket module for real-time event streaming
  - Block, transaction, address, contract event channels
  - Market and mining event streams
  - Subscription management with broadcast channels

- Implement API versioning strategy
  - URL path, header, and query parameter versioning
  - Version registry with deprecation support
  - Deprecation and sunset headers
2026-01-28 15:31:57 +05:30

633 lines
16 KiB
Rust

//! Synor blockchain CLI.
//!
//! Command-line interface for interacting with the Synor blockchain.
#![allow(dead_code)]
use std::path::PathBuf;
use clap::{Parser, Subcommand};
mod client;
mod commands;
mod config;
mod output;
mod wallet;
use crate::client::RpcClient;
use crate::config::CliConfig;
/// Synor blockchain CLI.
#[derive(Parser)]
#[command(name = "synor")]
#[command(version, about = "Synor blockchain CLI", long_about = None)]
struct Cli {
/// RPC server URL
#[arg(
short,
long,
env = "SYNOR_RPC_URL",
default_value = "http://127.0.0.1:16110"
)]
rpc: String,
/// Configuration file path
#[arg(short, long)]
config: Option<PathBuf>,
/// Output format (text, json)
#[arg(short, long, default_value = "text")]
output: String,
/// Enable verbose output
#[arg(short, long)]
verbose: bool,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
// ==================== Node Commands ====================
/// Get node information
Info,
/// Get node version
Version,
/// Get sync status
SyncStatus,
/// Get peer information
Peers,
// ==================== Block Commands ====================
/// Get block information
Block {
/// Block hash or height
id: String,
},
/// Get latest blocks
Blocks {
/// Number of blocks
#[arg(short, long, default_value = "10")]
count: usize,
},
/// Get current tips
Tips,
/// Get block count
BlockCount,
// ==================== Transaction Commands ====================
/// Get transaction information
Tx {
/// Transaction hash
hash: String,
},
/// Send transaction
Send {
/// Recipient address
to: String,
/// Amount in SYNOR
amount: String,
/// Fee in SYNOR (optional)
#[arg(short, long)]
fee: Option<String>,
},
/// Get mempool entries
Mempool {
/// Include transaction details
#[arg(short, long)]
verbose: bool,
},
// ==================== Wallet Commands ====================
/// Wallet operations
#[command(subcommand)]
Wallet(WalletCommands),
/// Get balance
Balance {
/// Address (uses wallet default if not specified)
address: Option<String>,
},
/// Get UTXOs
Utxos {
/// Address
address: String,
},
// ==================== Address Commands ====================
/// Validate an address
ValidateAddress {
/// Address to validate
address: String,
},
/// Decode an address
DecodeAddress {
/// Address to decode
address: String,
},
// ==================== Mining Commands ====================
/// Mining operations
#[command(subcommand)]
Mining(MiningCommands),
// ==================== Contract Commands ====================
/// Contract operations
#[command(subcommand)]
Contract(ContractCommands),
// ==================== Governance Commands ====================
/// Governance operations (DAO voting, treasury)
#[command(subcommand)]
Governance(GovernanceCommands),
// ==================== Hosting Commands ====================
/// Deploy web applications to Synor Hosting
#[command(subcommand)]
Deploy(DeployCommands),
// ==================== Network Commands ====================
/// Add a peer
AddPeer {
/// Peer address (host:port)
address: String,
},
/// Ban a peer
BanPeer {
/// Peer address or ID
peer: String,
},
/// Unban a peer
UnbanPeer {
/// Peer address or ID
peer: String,
},
// ==================== DEX Commands ====================
/// Decentralized exchange operations (trading, liquidity)
#[command(subcommand)]
Dex(commands::dex::DexCommands),
// ==================== IBC Commands ====================
/// Inter-Blockchain Communication (cross-chain transfers)
#[command(subcommand)]
Ibc(commands::ibc::IbcCommands),
// ==================== ZK Commands ====================
/// Zero-knowledge proof operations
#[command(subcommand)]
Zk(commands::zk::ZkCommands),
// ==================== Compiler Commands ====================
/// Smart contract compiler tools
#[command(subcommand)]
Compiler(commands::compiler::CompilerCommands),
}
#[derive(Subcommand)]
enum WalletCommands {
/// Create a new wallet (uses Hybrid keys: Ed25519 + Dilithium)
Create {
/// Wallet name
#[arg(short, long, default_value = "default")]
name: String,
},
/// Import wallet from seed phrase
Import {
/// Wallet name
#[arg(short, long, default_value = "default")]
name: String,
},
/// Export wallet
Export {
/// Wallet name
#[arg(short, long, default_value = "default")]
name: String,
},
/// List wallets
List,
/// Get wallet info
Info {
/// Wallet name
#[arg(short, long, default_value = "default")]
name: String,
},
/// Generate new address
NewAddress {
/// Wallet name
#[arg(short, long, default_value = "default")]
name: String,
},
/// List addresses
Addresses {
/// Wallet name
#[arg(short, long, default_value = "default")]
name: String,
},
}
#[derive(Subcommand)]
enum MiningCommands {
/// Get mining info
Info,
/// Get block template
Template {
/// Coinbase address
address: String,
},
/// Submit a mined block
Submit {
/// Block hex
block: String,
},
/// Estimate network hashrate
Hashrate,
}
#[derive(Subcommand)]
enum ContractCommands {
/// Deploy a contract
Deploy {
/// Path to WASM file
wasm: PathBuf,
/// Deployer address (bech32)
#[arg(short, long)]
deployer: String,
/// Constructor arguments (hex)
#[arg(short, long)]
args: Option<String>,
/// Gas limit
#[arg(short, long, default_value = "1000000")]
gas: u64,
},
/// Call a contract method
Call {
/// Contract ID (hex)
contract_id: String,
/// Method name
method: String,
/// Caller address (bech32)
#[arg(short, long)]
caller: String,
/// Arguments (hex)
#[arg(short, long)]
args: Option<String>,
/// Value to send
#[arg(short, long, default_value = "0")]
value: u64,
/// Gas limit
#[arg(short, long, default_value = "1000000")]
gas: u64,
},
/// Get contract code
Code {
/// Contract ID (hex)
contract_id: String,
},
/// Get contract storage
Storage {
/// Contract ID (hex)
contract_id: String,
/// Storage key (hex)
key: String,
},
/// Estimate gas for a call
EstimateGas {
/// Contract ID (hex)
contract_id: String,
/// Method name
method: String,
/// Caller address (bech32)
#[arg(short, long)]
caller: String,
/// Arguments (hex)
#[arg(short, long)]
args: Option<String>,
/// Value to send
#[arg(short, long, default_value = "0")]
value: u64,
},
/// Get contract metadata
Info {
/// Contract ID (hex)
contract_id: String,
},
}
#[derive(Subcommand)]
enum GovernanceCommands {
/// Get governance info
Info,
/// Get DAO statistics
Stats,
/// List proposals
Proposals {
/// Filter by state (active, pending, passed, defeated, executed)
#[arg(short, long)]
state: Option<String>,
},
/// Get proposal details
Proposal {
/// Proposal ID (hex)
id: String,
},
/// Create a proposal
CreateProposal {
/// Proposer address (bech32)
#[arg(short, long)]
proposer: String,
/// Proposal type (treasury_spend, ecosystem_grant, parameter_change, signaling)
#[arg(short = 't', long)]
proposal_type: String,
/// Proposal title
#[arg(long)]
title: String,
/// Proposal description
#[arg(short, long)]
description: String,
/// Recipient address (for treasury/grant proposals)
#[arg(long)]
recipient: Option<String>,
/// Amount in SYNOR (for treasury/grant proposals)
#[arg(long)]
amount: Option<u64>,
/// Parameter name (for parameter_change proposals)
#[arg(long)]
parameter: Option<String>,
/// Old value (for parameter_change proposals)
#[arg(long)]
old_value: Option<String>,
/// New value (for parameter_change proposals)
#[arg(long)]
new_value: Option<String>,
},
/// Vote on a proposal
Vote {
/// Proposal ID (hex)
#[arg(short, long)]
proposal_id: String,
/// Voter address (bech32)
#[arg(short, long)]
voter: String,
/// Vote choice (yes, no, abstain)
#[arg(short, long)]
choice: String,
/// Optional reason for the vote
#[arg(short, long)]
reason: Option<String>,
},
/// Execute a passed proposal
Execute {
/// Proposal ID (hex)
#[arg(short, long)]
proposal_id: String,
/// Executor address (bech32)
#[arg(short, long)]
executor: String,
},
/// Get treasury overview
Treasury,
/// Get treasury pool details
TreasuryPool {
/// Pool ID (hex)
id: String,
},
}
#[derive(Subcommand)]
enum DeployCommands {
/// Deploy the current project to Synor Hosting
Push {
/// Deployment name (defaults to directory name)
#[arg(short, long)]
name: Option<String>,
/// Output directory (defaults to synor.json build.output or "dist")
#[arg(short, long)]
output: Option<PathBuf>,
/// Hosting gateway URL
#[arg(long, env = "SYNOR_HOSTING_URL", default_value = "http://127.0.0.1:8280")]
gateway: String,
/// Skip running the build command
#[arg(long)]
skip_build: bool,
},
/// Initialize synor.json in the current directory
Init {
/// Deployment name
#[arg(short, long)]
name: Option<String>,
/// Enable SPA mode (fallback to index.html)
#[arg(long)]
spa: bool,
/// Output directory
#[arg(short, long)]
output: Option<String>,
},
/// List deployments
List {
/// Hosting gateway URL
#[arg(long, env = "SYNOR_HOSTING_URL", default_value = "http://127.0.0.1:8280")]
gateway: String,
},
/// Delete a deployment
Delete {
/// Deployment name
name: String,
/// Hosting gateway URL
#[arg(long, env = "SYNOR_HOSTING_URL", default_value = "http://127.0.0.1:8280")]
gateway: String,
},
/// Show deployment info
Info {
/// Deployment name
name: String,
/// Hosting gateway URL
#[arg(long, env = "SYNOR_HOSTING_URL", default_value = "http://127.0.0.1:8280")]
gateway: String,
},
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
// Initialize logging
if cli.verbose {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.init();
}
// Load config
let config = CliConfig::load_or_default(cli.config.as_deref());
// Create RPC client
let client = RpcClient::new(&cli.rpc);
// Set output format
let output = output::OutputFormat::from_str(&cli.output);
// Execute command
let result = match cli.command {
// Node commands
Commands::Info => commands::node::info(&client, output).await,
Commands::Version => commands::node::version(&client, output).await,
Commands::SyncStatus => commands::node::sync_status(&client, output).await,
Commands::Peers => commands::node::peers(&client, output).await,
// Block commands
Commands::Block { id } => commands::block::get_block(&client, &id, output).await,
Commands::Blocks { count } => commands::block::get_blocks(&client, count, output).await,
Commands::Tips => commands::block::get_tips(&client, output).await,
Commands::BlockCount => commands::block::get_block_count(&client, output).await,
// Transaction commands
Commands::Tx { hash } => commands::tx::get_tx(&client, &hash, output).await,
Commands::Send { to, amount, fee } => {
commands::tx::send(&client, &config, &to, &amount, fee.as_deref(), output).await
}
Commands::Mempool { verbose } => commands::tx::mempool(&client, verbose, output).await,
// Wallet commands
Commands::Wallet(cmd) => commands::wallet::handle(&config, cmd, output).await,
Commands::Balance { address } => {
commands::wallet::balance(&client, &config, address.as_deref(), output).await
}
Commands::Utxos { address } => commands::wallet::utxos(&client, &address, output).await,
// Address commands
Commands::ValidateAddress { address } => {
commands::address::validate(&address, output).await
}
Commands::DecodeAddress { address } => commands::address::decode(&address, output).await,
// Mining commands
Commands::Mining(cmd) => commands::mining::handle(&client, cmd, output).await,
// Contract commands
Commands::Contract(cmd) => commands::contract::handle(&client, &config, cmd, output).await,
// Governance commands
Commands::Governance(cmd) => commands::governance::handle(&client, cmd, output).await,
// Deploy commands
Commands::Deploy(cmd) => match cmd {
DeployCommands::Push {
name,
output: out_dir,
gateway,
skip_build,
} => commands::deploy::deploy(name, out_dir, &gateway, skip_build, output).await,
DeployCommands::Init { name, spa, output: out_dir } => {
commands::deploy::init(name, spa, out_dir, output)
}
DeployCommands::List { gateway } => commands::deploy::list(&gateway, output).await,
DeployCommands::Delete { name, gateway } => {
commands::deploy::delete(&name, &gateway, output).await
}
DeployCommands::Info { name, gateway } => {
// TODO: Implement info command
output::print_info(&format!("Deployment info for: {}", name));
output::print_kv("Gateway", &gateway);
Ok(())
}
},
// Network commands
Commands::AddPeer { address } => {
commands::network::add_peer(&client, &address, output).await
}
Commands::BanPeer { peer } => commands::network::ban_peer(&client, &peer, output).await,
Commands::UnbanPeer { peer } => commands::network::unban_peer(&client, &peer, output).await,
// DEX commands
Commands::Dex(cmd) => commands::dex::handle(&client, cmd, output).await,
// IBC commands
Commands::Ibc(cmd) => commands::ibc::handle(&client, cmd, output).await,
// ZK commands
Commands::Zk(cmd) => commands::zk::handle(&client, cmd, output).await,
// Compiler commands
Commands::Compiler(cmd) => commands::compiler::handle(&client, cmd, output).await,
};
if let Err(e) = result {
eprintln!("Error: {}", e);
std::process::exit(1);
}
}