208 lines
5.5 KiB
Rust
208 lines
5.5 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use anyhow::{Result, Context};
|
|
use reqwest::Client;
|
|
use std::sync::Arc;
|
|
|
|
/// NAC区块链RPC客户端
|
|
#[derive(Clone)]
|
|
pub struct NacClient {
|
|
client: Arc<Client>,
|
|
rpc_url: String,
|
|
}
|
|
|
|
impl NacClient {
|
|
pub fn new(rpc_url: String) -> Self {
|
|
Self {
|
|
client: Arc::new(Client::new()),
|
|
rpc_url,
|
|
}
|
|
}
|
|
|
|
/// 获取账户余额
|
|
pub async fn get_balance(&self, address: &str) -> Result<BalanceInfo> {
|
|
let request = RpcRequest {
|
|
jsonac_lens: "2.0".to_string(),
|
|
method: "nac_getBalance".to_string(),
|
|
params: vec![address.to_string()],
|
|
id: 1,
|
|
};
|
|
|
|
let response: RpcResponse<BalanceInfo> = self
|
|
.client
|
|
.post(&self.rpc_url)
|
|
.json(&request)
|
|
.send()
|
|
.await
|
|
.context("Failed to send RPC request")?
|
|
.json()
|
|
.await
|
|
.context("Failed to parse RPC response")?;
|
|
|
|
response.result.ok_or_else(|| anyhow::anyhow!("No result in RPC response"))
|
|
}
|
|
|
|
/// 发送交易
|
|
pub async fn send_transaction(&self, tx: Transaction) -> Result<String> {
|
|
let request = RpcRequest {
|
|
jsonac_lens: "2.0".to_string(),
|
|
method: "nac_sendTransaction".to_string(),
|
|
params: vec![serde_json::to_string(&tx)?],
|
|
id: 1,
|
|
};
|
|
|
|
let response: RpcResponse<String> = self
|
|
.client
|
|
.post(&self.rpc_url)
|
|
.json(&request)
|
|
.send()
|
|
.await
|
|
.context("Failed to send transaction")?
|
|
.json()
|
|
.await
|
|
.context("Failed to parse transaction response")?;
|
|
|
|
response.result.ok_or_else(|| anyhow::anyhow!("No transaction hash in response"))
|
|
}
|
|
|
|
/// 获取交易历史
|
|
pub async fn get_transactions(&self, address: &str, limit: u32) -> Result<Vec<TransactionInfo>> {
|
|
let request = RpcRequest {
|
|
jsonac_lens: "2.0".to_string(),
|
|
method: "nac_getTransactions".to_string(),
|
|
params: vec![address.to_string(), limit.to_string()],
|
|
id: 1,
|
|
};
|
|
|
|
let response: RpcResponse<Vec<TransactionInfo>> = self
|
|
.client
|
|
.post(&self.rpc_url)
|
|
.json(&request)
|
|
.send()
|
|
.await
|
|
.context("Failed to get transactions")?
|
|
.json()
|
|
.await
|
|
.context("Failed to parse transactions response")?;
|
|
|
|
response.result.ok_or_else(|| anyhow::anyhow!("No transactions in response"))
|
|
}
|
|
|
|
/// 获取交易详情
|
|
pub async fn get_transaction(&self, tx_hash: &str) -> Result<TransactionInfo> {
|
|
let request = RpcRequest {
|
|
jsonac_lens: "2.0".to_string(),
|
|
method: "nac_getTransaction".to_string(),
|
|
params: vec![tx_hash.to_string()],
|
|
id: 1,
|
|
};
|
|
|
|
let response: RpcResponse<TransactionInfo> = self
|
|
.client
|
|
.post(&self.rpc_url)
|
|
.json(&request)
|
|
.send()
|
|
.await
|
|
.context("Failed to get transaction")?
|
|
.json()
|
|
.await
|
|
.context("Failed to parse transaction response")?;
|
|
|
|
response.result.ok_or_else(|| anyhow::anyhow!("Transaction not found"))
|
|
}
|
|
|
|
/// 获取区块高度
|
|
pub async fn get_block_height(&self) -> Result<u64> {
|
|
let request = RpcRequest {
|
|
jsonac_lens: "2.0".to_string(),
|
|
method: "nac_blockNumber".to_string(),
|
|
params: vec![],
|
|
id: 1,
|
|
};
|
|
|
|
let response: RpcResponse<String> = self
|
|
.client
|
|
.post(&self.rpc_url)
|
|
.json(&request)
|
|
.send()
|
|
.await
|
|
.context("Failed to get block height")?
|
|
.json()
|
|
.await
|
|
.context("Failed to parse block height response")?;
|
|
|
|
let height_str = response.result.ok_or_else(|| anyhow::anyhow!("No block height in response"))?;
|
|
height_str.parse::<u64>().context("Failed to parse block height")
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
struct RpcRequest {
|
|
jsonac_lens: String,
|
|
method: String,
|
|
params: Vec<String>,
|
|
id: u64,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
struct RpcResponse<T> {
|
|
jsonac_lens: String,
|
|
result: Option<T>,
|
|
error: Option<RpcError>,
|
|
id: u64,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
struct RpcError {
|
|
code: i32,
|
|
message: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct BalanceInfo {
|
|
pub address: String,
|
|
pub balance: String,
|
|
pub assets: Vec<AssetBalance>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AssetBalance {
|
|
pub symbol: String,
|
|
pub amount: String,
|
|
pub decimals: u8,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Transaction {
|
|
pub from: String,
|
|
pub to: String,
|
|
pub amount: String,
|
|
pub asset: String,
|
|
pub nonce: u64,
|
|
pub signature: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TransactionInfo {
|
|
pub hash: String,
|
|
pub from: String,
|
|
pub to: String,
|
|
pub amount: String,
|
|
pub asset: String,
|
|
pub block_number: u64,
|
|
pub timestamp: i64,
|
|
pub status: String,
|
|
pub fee: String,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn test_client_creation() {
|
|
let client = NacClient::new("http://localhost:8545".to_string());
|
|
// 验证客户端创建成功
|
|
assert!(Arc::strong_count(&client.client) >= 1);
|
|
}
|
|
}
|