//! 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(&self, method: &str, params: Value) -> Result { 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 = 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 { self.call("synor_getInfo", json!([])).await } /// Gets server version. pub async fn get_version(&self) -> Result { self.call("synor_getServerVersion", json!([])).await } /// Gets connected peers. pub async fn get_peer_info(&self) -> Result> { 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 { self.call("synor_getBlock", json!([hash, include_txs])) .await } /// Gets block header. pub async fn get_block_header(&self, hash: &str) -> Result { self.call("synor_getBlockHeader", json!([hash])).await } /// Gets block count. pub async fn get_block_count(&self) -> Result { self.call("synor_getBlockCount", json!([])).await } /// Gets current tips. pub async fn get_tips(&self) -> Result> { self.call("synor_getTips", json!([])).await } /// Gets blue score. pub async fn get_blue_score(&self) -> Result { 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> { self.call( "synor_getBlocksByBlueScore", json!([blue_score, include_txs]), ) .await } // ==================== Transaction Methods ==================== /// Gets a transaction. pub async fn get_transaction(&self, hash: &str) -> Result { self.call("synor_getTransaction", json!([hash])).await } /// Submits a transaction. pub async fn submit_transaction(&self, tx_hex: &str) -> Result { self.call("synor_submitTransaction", json!([tx_hex])).await } /// Gets mempool entries. pub async fn get_mempool(&self) -> Result> { self.call("synor_getMempoolEntries", json!([])).await } /// Estimates fee. pub async fn estimate_fee(&self, priority: &str) -> Result { self.call("synor_estimateFee", json!([priority])).await } // ==================== UTXO Methods ==================== /// Gets UTXOs for an address. pub async fn get_utxos(&self, address: &str) -> Result> { self.call("synor_getUtxosByAddress", json!([address])).await } /// Gets balance for an address. pub async fn get_balance(&self, address: &str) -> Result { self.call("synor_getBalance", json!([address])).await } // ==================== Mining Methods ==================== /// Gets mining info. pub async fn get_mining_info(&self) -> Result { self.call("synor_getMiningInfo", json!([])).await } /// Gets block template. pub async fn get_block_template(&self, address: &str) -> Result { self.call("synor_getBlockTemplate", json!([address])).await } /// Submits a block. pub async fn submit_block(&self, block_hex: &str) -> Result { 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, ) -> Result { 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, ) -> Result { 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 { 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 { 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 { 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 { self.call( "synor_getContract", json!({ "contract_id": contract_id }), ) .await } // ==================== Network Methods ==================== /// Adds a peer. pub async fn add_peer(&self, address: &str) -> Result { self.call("synor_addPeer", json!([address])).await } /// Bans a peer. pub async fn ban_peer(&self, peer: &str) -> Result { self.call("synor_banPeer", json!([peer])).await } /// Unbans a peer. pub async fn unban_peer(&self, peer: &str) -> Result { self.call("synor_unbanPeer", json!([peer])).await } // ==================== Governance Methods ==================== /// Gets governance info. pub async fn get_governance_info(&self) -> Result { self.call("synor_getGovernanceInfo", json!([])).await } /// Gets DAO statistics. pub async fn get_dao_stats(&self) -> Result { self.call("synor_getDaoStats", json!([])).await } /// Gets active proposals. pub async fn get_active_proposals(&self) -> Result> { self.call("synor_getActiveProposals", json!([])).await } /// Gets proposals by state. pub async fn get_proposals_by_state(&self, state: &str) -> Result> { self.call("synor_getProposalsByState", json!([state])).await } /// Gets a proposal by ID. pub async fn get_proposal(&self, proposal_id: &str) -> Result { 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 { 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 { 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 { self.call( "synor_executeProposal", json!({ "proposal_id": proposal_id, "executor": executor }), ) .await } /// Gets treasury pools. pub async fn get_treasury_pools(&self) -> Result> { self.call("synor_getTreasuryPools", json!([])).await } /// Gets treasury pool by ID. pub async fn get_treasury_pool(&self, pool_id: &str) -> Result { self.call("synor_getTreasuryPool", json!([pool_id])).await } /// Gets total treasury balance. pub async fn get_treasury_balance(&self) -> Result { self.call("synor_getTreasuryBalance", json!([])).await } } // ==================== RPC Types ==================== #[derive(Debug, Deserialize)] struct RpcResponse { result: Option, error: Option, } #[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, } #[derive(Debug, Serialize, Deserialize)] pub struct BlockHeader { pub version: u32, pub parents: Vec, 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, pub outputs: Vec, 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, 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, } #[derive(Debug, Serialize, Deserialize)] pub struct BlockTemplate { pub header: BlockHeader, pub transactions: Vec, 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, } #[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, #[serde(default)] pub error: Option, } #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ContractLog { pub contract_id: String, pub topics: Vec, 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, } #[derive(Debug, Serialize, Deserialize)] pub struct GetCodeResult { pub code: Option, #[serde(default)] pub error: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct GetStorageResult { pub value: Option, #[serde(default)] pub error: Option, } #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ContractInfo { #[serde(default)] pub code_hash: Option, #[serde(default)] pub deployer: Option, #[serde(default)] pub deployed_at: Option, #[serde(default)] pub deployed_height: Option, #[serde(default)] pub error: Option, } // ==================== 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, } #[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, 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, #[serde(default)] pub error: Option, } #[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, } #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CreateProposalResult { pub proposal_id: String, pub number: u64, #[serde(default)] pub error: Option, } #[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, } #[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, } #[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, }