Skip to content

Commit

Permalink
RPC calls
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsosf committed Mar 14, 2024
1 parent 3b2b586 commit 8c0f659
Show file tree
Hide file tree
Showing 14 changed files with 1,807 additions and 10 deletions.
1,497 changes: 1,497 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
reqwest = { version = "0.11.25", features = ["json"] }
serde_json = { version = "1.0.114", features = ["preserve_order"] }
tokio = { version = "1.36.0", features = ["rt", "rt-multi-thread", "macros"] }
serde = { version = "1.0.197", features = ["derive"] }
num-bigint = "0.4.4"
num-traits = "0.2.18"
rust_decimal = "1.34.3"
39 changes: 39 additions & 0 deletions src/icon_service.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use std::error::Error;
use serde_json::{Value};
use crate::transaction_builder::TransactionBuilder;

pub async fn get_last_block() -> Result<Value, Box<dyn Error>> {
let transaction_builder = TransactionBuilder::new()
.method("icx_getLastBlock");

let response: Value = transaction_builder.send().await.map_err(|e| Box::new(e) as Box<dyn Error>)?;

Ok(response)
}

pub async fn get_block_by_height(block_height: &str) -> Result<Value, Box<dyn Error>> {
let transaction_builder = TransactionBuilder::new()
.method("icx_getBlockByHeight").block_height(block_height);

let response: Value = transaction_builder.send().await.map_err(|e| Box::new(e) as Box<dyn Error>)?;

Ok(response)
}

pub async fn get_block_by_hash(block_hash: &str) -> Result<Value, Box<dyn Error>> {
let transaction_builder = TransactionBuilder::new()
.method("icx_getBlockByHash").block_hash(block_hash);

let response: Value = transaction_builder.send().await.map_err(|e| Box::new(e) as Box<dyn Error>)?;

Ok(response)
}

pub async fn get_balance(address: &str) -> Result<Value, Box<dyn Error>> {
let transaction_builder = TransactionBuilder::new()
.method("icx_getBalance").address(address);

let response: Value = transaction_builder.send().await.map_err(|e| Box::new(e) as Box<dyn Error>)?;

Ok(response)
}
16 changes: 6 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
mod client;
pub mod utils;
pub mod icon_service;
mod query;
mod transaction;
pub mod transaction_builder;
mod wallet;

pub fn add(left: usize, right: usize) -> usize {
left + right
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
File renamed without changes.
Empty file added src/transaction.rs
Empty file.
87 changes: 87 additions & 0 deletions src/transaction_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use reqwest::{Client, Error};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value, Map};

#[derive(Default, Serialize, Deserialize)]
pub struct TransactionBuilder {
icon_service_url: Option<String>,
data: Value,
}

impl TransactionBuilder {
pub fn new() -> Self {
Self {
icon_service_url: None,
// Set default values for `data` here
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 {
// Use as_object_mut() to get a mutable reference to the data object
if let Some(obj) = self.data.as_object_mut() {
// Insert or modify the "method" field
obj.insert("method".to_string(), json!(method));
}
self
}

pub fn set_params(mut self, params: &Map<String, Value>) -> Self {
// Ensure `data` has a "params" object; create it if not
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();

// Insert or update the given parameters
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(&params)
}

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(&params)
}

pub fn address(self, address: &str) -> Self {
let mut params = Map::new();
params.insert("address".to_string(), json!(address));

self.set_params(&params)
}

pub async fn send(self) -> Result<Value, Error> {
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;
println!("{:?}", data);

let res = client.post(&url)
.json(&data)
.send()
.await?;

match res.status() {
reqwest::StatusCode::OK => Ok(res.json().await?),
other => panic!("HTTP Request Failed with status: {}", other),
}
}
}
Empty file added src/utils/decoder.rs
Empty file.
Empty file added src/utils/encoder.rs
Empty file.
29 changes: 29 additions & 0 deletions src/utils/helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use num_bigint::BigInt;
use num_traits::{Num, ToPrimitive};
use rust_decimal::Decimal;
use std::str::FromStr;

pub fn hex_to_icx(value: &str) -> Option<Decimal> {
// Strip the "0x" prefix if present and parse the remaining hex string into a BigInt
let clean_value = value.strip_prefix("0x").unwrap_or(value);
let value_bigint = BigInt::from_str_radix(clean_value, 16).ok();

// Convert BigInt to Decimal for arithmetic
let decimal_value = Decimal::from_str(&value_bigint?.to_str_radix(10)).ok();

// Create the divisor for 18 decimal places
let divisor = Decimal::new(10i64.pow(18), 0);

// Perform the division, scaling the result to 18 decimal places
Some(decimal_value? / divisor)
}

pub fn icx_to_hex(value: Decimal) -> Option<String> {
let multiplier = Decimal::from_str(&10u128.pow(18).to_string()).ok()?;
// Perform the multiplication to adjust for decimal places
let result_decimal = value * multiplier;
let result_bigint = result_decimal.to_i128().and_then(|val| Some(BigInt::from(val)))?;

// Convert BigInt to hexadecimal string and prefix with "0x"
Some(format!("0x{}", result_bigint.to_str_radix(16)))
}
4 changes: 4 additions & 0 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod encoder;
mod decoder;
pub mod helpers;
pub mod responses;
48 changes: 48 additions & 0 deletions src/utils/responses.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// use serde::{Deserialize, Serialize};
//
// #[derive(Serialize, Deserialize, Debug)]
// pub struct ApiResponse {
// jsonrpc: String,
// result: BlockResult,
// id: i32,
// }
//
// #[derive(Serialize, Deserialize, Debug)]
// struct BlockResult {
// block_hash: String,
// confirmed_transaction_list: Vec<Transaction>,
// height: i64,
// merkle_tree_root_hash: String,
// peer_id: String,
// prev_block_hash: String,
// signature: String,
// time_stamp: i64,
// version: String,
// }
//
// // Assuming transactions can be of different types, you might define an enum for them
// // For simplicity, I'll treat them as a single type here
// #[derive(Serialize, Deserialize, Debug)]
// #[serde(tag = "dataType", rename_all = "camelCase")]
// enum Transaction {
// Base {
// data: TransactionData,
// timestamp: String,
// txHash: String,
// version: String,
// },
// // Other types of transactions can be added here
// }
//
// #[derive(Serialize, Deserialize, Debug)]
// struct TransactionData {
// result: TransactionResult,
// }
//
// #[derive(Serialize, Deserialize, Debug)]
// #[serde(tag = "dataType", rename_all = "camelCase")]
// struct TransactionResult {
// covered_by_fee: String,
// covered_by_over_issued_ICX: String,
// issue: String,
// }
Empty file added src/wallet.rs
Empty file.
90 changes: 90 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use std::str::FromStr;
use rust_decimal::Decimal;
use icon_sdk_rust::icon_service;
use icon_sdk_rust::utils::helpers::hex_to_icx;

#[tokio::test]
async fn test_get_last_block() -> Result<(), ()> {
let res = icon_service::get_last_block().await;
match res {
Ok(response) => {
assert_eq!(response["jsonrpc"], "2.0");
assert!(!response.as_object().unwrap().contains_key("error"));
println!("{:?}", response);
},
Err(e) => println!("Error: {:?}", e),
}

Ok(())
}

#[tokio::test]
async fn test_get_block_by_height() -> Result<(), ()> {
let res = icon_service::get_block_by_height("0x0").await;
match res {
Ok(response) => {
assert_eq!(response["jsonrpc"], "2.0");
assert!(!response.as_object().unwrap().contains_key("error"));
println!("{:?}", response);
},
Err(e) => println!("Error: {:?}", e),
}

Ok(())
}

#[tokio::test]
async fn test_get_block_by_hash() -> Result<(), ()> {
let res = icon_service::get_block_by_hash("0xcf43b3fd45981431a0e64f79d07bfcf703e064b73b802c5f32834eec72142190").await;
match res {
Ok(response) => {
assert_eq!(response["jsonrpc"], "2.0");
assert!(!response.as_object().unwrap().contains_key("error"));
println!("{:?}", response);
},
Err(e) => println!("Error: {:?}", e),
}

Ok(())
}

#[tokio::test]
async fn test_get_balance() -> Result<(), ()> {
let res = icon_service::get_balance("hxd5ace539bf910635c2fa0e9c185d2d3c8d52c4cc").await;
match res {
Ok(response) => {
println!("{:?}", response);
assert_eq!(response["jsonrpc"], "2.0");
assert!(!response.as_object().unwrap().contains_key("error"));
},
Err(e) => println!("Error: {:?}", e),
}

Ok(())
}
#[tokio::test]
async fn test_hex_to_icx() -> Result<(), ()> {
let res = icon_sdk_rust::utils::helpers::hex_to_icx("0x63b5429420c741b16a10f");
match res {
Some(response) => {
assert_eq!(response.to_string(), "7533727.039631672546337039");
},
None => println!("Error"),
}

Ok(())
}

#[tokio::test]
async fn test_icx_to_hex() -> Result<(), ()> {
let res = icon_sdk_rust::utils::helpers::icx_to_hex(Decimal::from_str("7533727.039631672546337039").unwrap());
match res {
Some(response) => {
assert_eq!(response, "0x63d8bac040145a956a22a");
println!("{:?}", response);
},
None => println!("Error"),
}

Ok(())
}

0 comments on commit 8c0f659

Please sign in to comment.