use std::str::FromStr;
use reqwest::{Client, StatusCode};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value, Map};
use secp256k1::{Message, Secp256k1, SecretKey};
use hex::{encode, decode};
use crate::utils::serializer::Serializer;
use chrono::Utc;
use base64::{Engine as _, engine::{general_purpose as base64_encoder}};
use hex::FromHex;
use rust_decimal::Decimal;
use thiserror::Error;
use crate::utils::helpers::icx_to_hex;
#[derive(Default, Serialize, Deserialize)]
pub struct Transaction {
icon_service_url: Option<String>,
data: Value,
}
#[derive(Error, Debug)]
pub enum MyError {
#[error("request failed")]
Request(#[from] reqwest::Error),
#[error("unexpected response: {0}")]
UnexpectedResponse(String),
}
impl Transaction {
pub fn new() -> Self {
Self {
icon_service_url: None,
data: json!({
"jsonrpc": "2.0",
"id": 1234
}),
}
}
pub fn icon_service_url(mut self, url: &str) -> Self {
self.icon_service_url = Some(url.to_string());
self
}
pub fn method(mut self, method: &str) -> Self {
if let Some(obj) = self.data.as_object_mut() {
obj.insert("method".to_string(), json!(method));
}
self
}
pub fn set_params(mut self, params: &Map<String, Value>) -> Self {
let data_obj = self.data.as_object_mut().expect("data is not an object");
let params_obj = data_obj.entry("params").or_insert_with(|| json!({})).as_object_mut().unwrap();
for (key, value) in params {
params_obj.insert(key.clone(), value.clone());
}
self
}
pub fn block_height(self, block_height: &str) -> Self {
let mut params = Map::new();
params.insert("height".to_string(), json!(block_height));
self.set_params(¶ms)
}
pub fn block_hash(self, block_hash: &str) -> Self {
let mut params = Map::new();
params.insert("hash".to_string(), json!(block_hash));
self.set_params(¶ms)
}
pub fn address(self, address: &str) -> Self {
let mut params = Map::new();
params.insert("address".to_string(), json!(address));
self.set_params(¶ms)
}
pub fn from(self, from: &str) -> Self {
let mut params = Map::new();
params.insert("from".to_string(), json!(from));
self.set_params(¶ms)
}
pub fn to(self, to: &str) -> Self {
let mut params = Map::new();
params.insert("to".to_string(), json!(to));
self.set_params(¶ms)
}
pub fn value(self, value: &str) -> Self {
let mut params = Map::new();
let mut parsed_value = value.to_string();
if !parsed_value.starts_with("0x") {
match icx_to_hex(Decimal::from_str(value).expect("Invalid value")) {
Some(v) => {
parsed_value = v;
}
None => panic!("Failed to convert value to hex"),
}
}
params.insert("value".to_string(), json!(parsed_value));
self.set_params(¶ms)
}
pub fn version(self, version: &str) -> Self {
let mut params = Map::new();
params.insert("version".to_string(), json!(version));
self.set_params(¶ms)
}
pub fn nid(self, nid: &str) -> Self {
let mut params = Map::new();
params.insert("nid".to_string(), json!(nid));
self.set_params(¶ms)
}
pub fn nonce(self, nonce: &str) -> Self {
let mut params = Map::new();
params.insert("nonce".to_string(), json!(nonce));
self.set_params(¶ms)
}
pub fn step_limit(self, step_limit: &str) -> Self {
let mut params = Map::new();
params.insert("stepLimit".to_string(), json!(step_limit));
self.set_params(¶ms)
}
pub fn timestamp(self) -> Self {
let now = Utc::now();
let timestamp_in_micros = now.timestamp_micros();
let hex_timestamp = format!("0x{:x}", timestamp_in_micros);
let mut params = Map::new();
params.insert("timestamp".to_string(), json!(hex_timestamp.to_string()));
self.set_params(¶ms)
}
pub fn sign(self, private_key: &str) -> Self {
let serialized_transaction = Serializer::serialize_transaction(&self.data["params"], true);
let serialized_transaction_bytes = Vec::from_hex(serialized_transaction).expect("Invalid hex string");
let secp = Secp256k1::new();
let secret_key = SecretKey::from_slice(&decode(private_key).expect("Invalid private key")).expect("Failed to create secret key");
let message = Message::from_digest_slice(serialized_transaction_bytes.as_slice()).expect("Failed to create message");
let sig = secp.sign_ecdsa_recoverable(&message, &secret_key);
let (rec_id, sig_bytes) = sig.serialize_compact();
let signature = format!("{}{:02x}", encode(sig_bytes), rec_id.to_i32());
let signature_bytes = decode(signature).expect("Failed to decode hex");
let transaction_signature = base64_encoder::STANDARD.encode(signature_bytes);
let mut params = Map::new();
params.insert("signature".to_string(), json!(transaction_signature));
self.set_params(¶ms)
}
pub fn get_signature(self) -> Value{
self.data["params"]["signature"].clone()
}
pub async fn send(self) -> Result<Value, MyError> {
let client = Client::new();
let url = self.icon_service_url.unwrap_or_else(|| "https://api.icon.community/api/v3".to_string());
let data = self.data;
let res = client.post(&url)
.json(&data)
.send()
.await?;
match res.status() {
StatusCode::OK => Ok(res.json().await?),
_ => {
let error_message = res.text().await.unwrap_or_else(|_| "Unknown error".to_string());
Err(MyError::UnexpectedResponse(error_message))
},
}
}
}