use std::format as f;
use serde::Deserialize;
use serde::Serialize;
use crate::clarity;
use crate::clarity::Codec;
use crate::clarity::FnArguments;
use crate::transaction::Transaction;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
IO(#[from] std::io::Error),
#[error(transparent)]
Ureq(#[from] ureq::Error),
#[error(transparent)]
Clarity(#[from] clarity::Error),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct NodeInfoResponse {
pub peer_version: u64,
pub pox_consensus: String,
pub burn_block_height: u64,
pub stable_pox_consensus: String,
pub stable_burn_block_height: u64,
pub server_version: String,
pub network_id: u32,
pub parent_network_id: u32,
pub stacks_tip_height: u64,
pub stacks_tip: String,
pub stacks_tip_consensus_hash: String,
pub genesis_chainstate_hash: String,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct AddressInfoResponse {
pub balance: String,
pub locked: String,
pub unlock_height: u64,
pub nonce: u64,
pub balance_proof: String,
pub nonce_proof: String,
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
#[serde(untagged)]
pub enum EstimateFeeResponse {
Ok(FeeOk),
Err(String),
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct FeeOk {
pub estimations: Vec<FeeEstimate>,
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct FeeEstimate {
pub fee: u64,
pub fee_rate: f64,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(untagged)]
pub enum BroadcastResponse {
Ok(String),
Err(BroadcastErr),
}
impl std::fmt::Display for BroadcastResponse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BroadcastResponse::Ok(str) => write!(f, "{str}",),
BroadcastResponse::Err(err) => write!(f, "{err}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct BroadcastErr {
pub error: String,
pub reason: String,
pub txid: String,
}
impl std::fmt::Display for BroadcastErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "message: {}, reason: {}", self.error, self.reason)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ReadOnlyResponse {
Ok(ReadOnlyOk),
Err(ReadOnlyErr),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ReadOnlyOk {
pub okay: bool,
pub result: String,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ReadOnlyErr {
pub okay: bool,
pub cause: String,
}
#[derive(Debug, Clone)]
pub struct StacksRPC {
__url: String,
}
impl StacksRPC {
pub fn new<T>(url: T) -> Self
where
T: Into<String>,
{
Self { __url: url.into() }
}
pub fn info(&self) -> Result<NodeInfoResponse, Error> {
let request = ureq::get(&f!("{}/v2/info", self.__url));
Ok(request.call()?.into_json::<NodeInfoResponse>()?)
}
pub fn address<T>(&self, addr: T) -> Result<AddressInfoResponse, Error>
where
T: Into<String>,
{
let request = ureq::get(&f!("{}/v2/accounts/{}", self.__url, addr.into()));
Ok(request.call()?.into_json::<AddressInfoResponse>()?)
}
pub fn estimate_fee(&self, transaction: &Transaction) -> Result<EstimateFeeResponse, Error> {
let response = ureq::post(&f!("{}/v2/fees/transaction", self.__url))
.send_json(ureq::json!({"transaction_payload": transaction.payload.hex()?, "estimated_len": transaction.len()?}));
match response {
Ok(res) => Ok(EstimateFeeResponse::Ok(res.into_json::<FeeOk>()?)),
Err(err) => {
if let ureq::Error::Status(_, res) = err {
Ok(EstimateFeeResponse::Err(res.into_string()?))
} else {
Err(Error::Ureq(err))
}
}
}
}
pub fn broadcast(&self, transaction: &Transaction) -> Result<BroadcastResponse, Error> {
let response = ureq::post(&f!("{}/v2/transactions", self.__url))
.set("Content-Type", "application/octet-stream")
.send_bytes(&transaction.encode()?);
match response {
Ok(res) => Ok(BroadcastResponse::Ok(res.into_string()?)),
Err(err) => {
if let ureq::Error::Status(_, res) = err {
let err = res.into_json::<BroadcastErr>()?;
Ok(BroadcastResponse::Err(err))
} else {
Err(Error::Ureq(err))
}
}
}
}
pub fn read_only(
&self,
contract_addr: &str,
contract_name: &str,
fn_name: &str,
fn_args: FnArguments,
sender: &str,
) -> Result<ReadOnlyResponse, Error> {
let req = ureq::post(&f!(
"{}/v2/contracts/call-read/{}/{}/{}",
self.__url,
contract_addr,
contract_name,
fn_name
));
let arguments = fn_args
.into_iter()
.map(|a| a.hex())
.collect::<Result<Vec<String>, _>>()?;
let response = req.send_json(ureq::json!({
"sender": sender,
"arguments": arguments,
}))?;
Ok(response.into_json::<ReadOnlyResponse>()?)
}
}