694 lines
18 KiB
Rust
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,
|
|
}
|