synor/apps/cli/src/client.rs
2026-01-08 05:22:24 +05:30

694 lines
18 KiB
Rust

//! RPC client for communicating with synord.
use anyhow::Result;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::{json, Value};
/// RPC client.
#[derive(Clone)]
pub struct RpcClient {
url: String,
client: reqwest::Client,
}
impl RpcClient {
/// Creates a new RPC client.
pub fn new(url: &str) -> Self {
RpcClient {
url: url.to_string(),
client: reqwest::Client::new(),
}
}
/// Makes an RPC call.
pub async fn call<T: DeserializeOwned>(&self, method: &str, params: Value) -> Result<T> {
let request = json!({
"jsonrpc": "2.0",
"id": 1,
"method": method,
"params": params
});
let response = self.client.post(&self.url).json(&request).send().await?;
let rpc_response: RpcResponse<T> = response.json().await?;
if let Some(error) = rpc_response.error {
anyhow::bail!("RPC error {}: {}", error.code, error.message);
}
rpc_response
.result
.ok_or_else(|| anyhow::anyhow!("No result in response"))
}
// ==================== Node Methods ====================
/// Gets node info.
pub async fn get_info(&self) -> Result<NodeInfo> {
self.call("synor_getInfo", json!([])).await
}
/// Gets server version.
pub async fn get_version(&self) -> Result<ServerVersion> {
self.call("synor_getServerVersion", json!([])).await
}
/// Gets connected peers.
pub async fn get_peer_info(&self) -> Result<Vec<PeerInfo>> {
self.call("synor_getPeerInfo", json!([])).await
}
// ==================== Block Methods ====================
/// Gets a block by hash.
pub async fn get_block(&self, hash: &str, include_txs: bool) -> Result<Block> {
self.call("synor_getBlock", json!([hash, include_txs]))
.await
}
/// Gets block header.
pub async fn get_block_header(&self, hash: &str) -> Result<BlockHeader> {
self.call("synor_getBlockHeader", json!([hash])).await
}
/// Gets block count.
pub async fn get_block_count(&self) -> Result<u64> {
self.call("synor_getBlockCount", json!([])).await
}
/// Gets current tips.
pub async fn get_tips(&self) -> Result<Vec<String>> {
self.call("synor_getTips", json!([])).await
}
/// Gets blue score.
pub async fn get_blue_score(&self) -> Result<u64> {
self.call("synor_getBlueScore", json!([])).await
}
/// Gets blocks by blue score.
pub async fn get_blocks_by_blue_score(
&self,
blue_score: u64,
include_txs: bool,
) -> Result<Vec<Block>> {
self.call(
"synor_getBlocksByBlueScore",
json!([blue_score, include_txs]),
)
.await
}
// ==================== Transaction Methods ====================
/// Gets a transaction.
pub async fn get_transaction(&self, hash: &str) -> Result<Transaction> {
self.call("synor_getTransaction", json!([hash])).await
}
/// Submits a transaction.
pub async fn submit_transaction(&self, tx_hex: &str) -> Result<String> {
self.call("synor_submitTransaction", json!([tx_hex])).await
}
/// Gets mempool entries.
pub async fn get_mempool(&self) -> Result<Vec<MempoolEntry>> {
self.call("synor_getMempoolEntries", json!([])).await
}
/// Estimates fee.
pub async fn estimate_fee(&self, priority: &str) -> Result<u64> {
self.call("synor_estimateFee", json!([priority])).await
}
// ==================== UTXO Methods ====================
/// Gets UTXOs for an address.
pub async fn get_utxos(&self, address: &str) -> Result<Vec<Utxo>> {
self.call("synor_getUtxosByAddress", json!([address])).await
}
/// Gets balance for an address.
pub async fn get_balance(&self, address: &str) -> Result<Balance> {
self.call("synor_getBalance", json!([address])).await
}
// ==================== Mining Methods ====================
/// Gets mining info.
pub async fn get_mining_info(&self) -> Result<MiningInfo> {
self.call("synor_getMiningInfo", json!([])).await
}
/// Gets block template.
pub async fn get_block_template(&self, address: &str) -> Result<BlockTemplate> {
self.call("synor_getBlockTemplate", json!([address])).await
}
/// Submits a block.
pub async fn submit_block(&self, block_hex: &str) -> Result<String> {
self.call("synor_submitBlock", json!([block_hex])).await
}
// ==================== Contract Methods ====================
/// Deploys a contract.
pub async fn deploy_contract(
&self,
bytecode: &str,
init_args: &str,
deployer: &str,
gas_limit: Option<u64>,
) -> Result<DeployResult> {
self.call(
"synor_deployContract",
json!({
"bytecode": bytecode,
"init_args": init_args,
"deployer": deployer,
"gas_limit": gas_limit
}),
)
.await
}
/// Calls a contract method.
pub async fn call_contract(
&self,
contract_id: &str,
method: &str,
args: &str,
caller: &str,
value: u64,
gas_limit: Option<u64>,
) -> Result<ContractResult> {
self.call(
"synor_callContract",
json!({
"contract_id": contract_id,
"method": method,
"args": args,
"caller": caller,
"value": value,
"gas_limit": gas_limit
}),
)
.await
}
/// Estimates gas for a contract call.
pub async fn estimate_gas(
&self,
contract_id: &str,
method: &str,
args: &str,
caller: &str,
value: u64,
) -> Result<EstimateGasResult> {
self.call(
"synor_estimateGas",
json!({
"contract_id": contract_id,
"method": method,
"args": args,
"caller": caller,
"value": value
}),
)
.await
}
/// Gets contract bytecode.
pub async fn get_contract_code(&self, contract_id: &str) -> Result<GetCodeResult> {
self.call(
"synor_getCode",
json!({
"contract_id": contract_id
}),
)
.await
}
/// Gets contract storage value.
pub async fn get_contract_storage(
&self,
contract_id: &str,
key: &str,
) -> Result<GetStorageResult> {
self.call(
"synor_getStorageAt",
json!({
"contract_id": contract_id,
"key": key
}),
)
.await
}
/// Gets contract metadata.
pub async fn get_contract(&self, contract_id: &str) -> Result<ContractInfo> {
self.call(
"synor_getContract",
json!({
"contract_id": contract_id
}),
)
.await
}
// ==================== Network Methods ====================
/// Adds a peer.
pub async fn add_peer(&self, address: &str) -> Result<bool> {
self.call("synor_addPeer", json!([address])).await
}
/// Bans a peer.
pub async fn ban_peer(&self, peer: &str) -> Result<bool> {
self.call("synor_banPeer", json!([peer])).await
}
/// Unbans a peer.
pub async fn unban_peer(&self, peer: &str) -> Result<bool> {
self.call("synor_unbanPeer", json!([peer])).await
}
// ==================== Governance Methods ====================
/// Gets governance info.
pub async fn get_governance_info(&self) -> Result<GovernanceInfo> {
self.call("synor_getGovernanceInfo", json!([])).await
}
/// Gets DAO statistics.
pub async fn get_dao_stats(&self) -> Result<DaoStats> {
self.call("synor_getDaoStats", json!([])).await
}
/// Gets active proposals.
pub async fn get_active_proposals(&self) -> Result<Vec<ProposalSummary>> {
self.call("synor_getActiveProposals", json!([])).await
}
/// Gets proposals by state.
pub async fn get_proposals_by_state(&self, state: &str) -> Result<Vec<ProposalSummary>> {
self.call("synor_getProposalsByState", json!([state])).await
}
/// Gets a proposal by ID.
pub async fn get_proposal(&self, proposal_id: &str) -> Result<ProposalInfo> {
self.call("synor_getProposal", json!([proposal_id])).await
}
/// Creates a proposal.
pub async fn create_proposal(
&self,
proposer: &str,
proposal_type: &str,
title: &str,
description: &str,
params: serde_json::Value,
) -> Result<CreateProposalResult> {
self.call(
"synor_createProposal",
json!({
"proposer": proposer,
"proposal_type": proposal_type,
"title": title,
"description": description,
"params": params
}),
)
.await
}
/// Casts a vote on a proposal.
pub async fn vote(
&self,
proposal_id: &str,
voter: &str,
choice: &str,
reason: Option<&str>,
) -> Result<VoteResult> {
self.call(
"synor_vote",
json!({
"proposal_id": proposal_id,
"voter": voter,
"choice": choice,
"reason": reason
}),
)
.await
}
/// Executes a passed proposal.
pub async fn execute_proposal(
&self,
proposal_id: &str,
executor: &str,
) -> Result<ExecuteProposalResult> {
self.call(
"synor_executeProposal",
json!({
"proposal_id": proposal_id,
"executor": executor
}),
)
.await
}
/// Gets treasury pools.
pub async fn get_treasury_pools(&self) -> Result<Vec<TreasuryPoolInfo>> {
self.call("synor_getTreasuryPools", json!([])).await
}
/// Gets treasury pool by ID.
pub async fn get_treasury_pool(&self, pool_id: &str) -> Result<TreasuryPoolInfo> {
self.call("synor_getTreasuryPool", json!([pool_id])).await
}
/// Gets total treasury balance.
pub async fn get_treasury_balance(&self) -> Result<u64> {
self.call("synor_getTreasuryBalance", json!([])).await
}
}
// ==================== RPC Types ====================
#[derive(Debug, Deserialize)]
struct RpcResponse<T> {
result: Option<T>,
error: Option<RpcError>,
}
#[derive(Debug, Deserialize)]
struct RpcError {
code: i32,
message: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct NodeInfo {
pub version: String,
pub protocol_version: u32,
pub network: String,
pub peer_count: usize,
pub synced: bool,
pub block_count: u64,
pub blue_score: u64,
pub mempool_size: usize,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ServerVersion {
pub version: String,
pub name: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PeerInfo {
pub id: String,
pub address: String,
pub is_inbound: bool,
pub version: u32,
pub user_agent: String,
pub latency_ms: u32,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Block {
pub hash: String,
pub header: BlockHeader,
pub transactions: Vec<Transaction>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BlockHeader {
pub version: u32,
pub parents: Vec<String>,
pub hash_merkle_root: String,
pub utxo_commitment: String,
pub timestamp: u64,
pub bits: u32,
pub nonce: u64,
pub blue_score: u64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Transaction {
pub hash: String,
pub inputs: Vec<TxInput>,
pub outputs: Vec<TxOutput>,
pub mass: u64,
pub fee: u64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct TxInput {
pub previous_outpoint: Outpoint,
pub signature_script: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Outpoint {
pub transaction_id: String,
pub index: u32,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct TxOutput {
pub value: u64,
pub script_public_key: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MempoolEntry {
pub hash: String,
pub fee: u64,
pub mass: u64,
pub timestamp: u64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Utxo {
pub outpoint: Outpoint,
pub amount: u64,
pub script_public_key: String,
pub block_hash: Option<String>,
pub is_coinbase: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Balance {
pub confirmed: u64,
pub unconfirmed: u64,
pub total: u64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MiningInfo {
pub blocks: u64,
pub difficulty: f64,
pub network_hashrate: f64,
pub pool_hashrate: Option<f64>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BlockTemplate {
pub header: BlockHeader,
pub transactions: Vec<Transaction>,
pub target: String,
pub is_synced: bool,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DeployResult {
pub contract_id: String,
pub address: String,
pub gas_used: u64,
#[serde(default)]
pub error: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ContractResult {
#[serde(default)]
pub success: bool,
#[serde(default)]
pub data: String,
#[serde(default)]
pub gas_used: u64,
#[serde(default)]
pub logs: Vec<ContractLog>,
#[serde(default)]
pub error: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ContractLog {
pub contract_id: String,
pub topics: Vec<String>,
pub data: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EstimateGasResult {
#[serde(default)]
pub estimated_gas: u64,
#[serde(default)]
pub error: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GetCodeResult {
pub code: Option<String>,
#[serde(default)]
pub error: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GetStorageResult {
pub value: Option<String>,
#[serde(default)]
pub error: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ContractInfo {
#[serde(default)]
pub code_hash: Option<String>,
#[serde(default)]
pub deployer: Option<String>,
#[serde(default)]
pub deployed_at: Option<u64>,
#[serde(default)]
pub deployed_height: Option<u64>,
#[serde(default)]
pub error: Option<String>,
}
// ==================== Governance Types ====================
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GovernanceInfo {
pub proposal_threshold: u64,
pub quorum_bps: u32,
pub voting_period_blocks: u64,
pub execution_delay_blocks: u64,
pub total_proposals: u64,
pub active_proposals: u64,
pub total_treasury_balance: u64,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DaoStats {
pub total_proposals: u64,
pub active_proposals: u64,
pub passed_proposals: u64,
pub defeated_proposals: u64,
pub executed_proposals: u64,
pub total_votes_cast: u64,
pub council_members: usize,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProposalSummary {
pub id: String,
pub number: u64,
pub title: String,
pub proposer: String,
pub state: String,
pub yes_votes: u64,
pub no_votes: u64,
pub abstain_votes: u64,
pub total_voters: usize,
pub yes_percentage: f64,
pub participation_rate: f64,
pub has_quorum: bool,
pub time_remaining_blocks: Option<u64>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProposalInfo {
pub id: String,
pub number: u64,
pub proposer: String,
pub proposal_type: String,
pub title: String,
pub description: String,
pub discussion_url: Option<String>,
pub created_at_block: u64,
pub voting_starts_block: u64,
pub voting_ends_block: u64,
pub execution_allowed_block: u64,
pub state: String,
pub yes_votes: u64,
pub no_votes: u64,
pub abstain_votes: u64,
pub votes: Vec<VoteInfo>,
#[serde(default)]
pub error: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VoteInfo {
pub voter: String,
pub choice: String,
pub power: u64,
pub weight: u64,
pub voted_at_block: u64,
pub reason: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateProposalResult {
pub proposal_id: String,
pub number: u64,
#[serde(default)]
pub error: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VoteResult {
pub success: bool,
pub proposal_id: String,
pub voter: String,
pub choice: String,
#[serde(default)]
pub error: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExecuteProposalResult {
pub success: bool,
pub proposal_id: String,
pub executed_at: u64,
#[serde(default)]
pub error: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TreasuryPoolInfo {
pub id: String,
pub name: String,
pub balance: u64,
pub total_deposited: u64,
pub total_spent: u64,
pub frozen: bool,
}