Skip to content

Commit

Permalink
Add Support of fast near APIs in near-providers
Browse files Browse the repository at this point in the history
  • Loading branch information
ckshitij committed Jun 10, 2024
1 parent c7628e3 commit 5bce33f
Show file tree
Hide file tree
Showing 9 changed files with 309 additions and 4 deletions.
7 changes: 4 additions & 3 deletions near-accounts/examples/view_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ async fn single_thread() -> Result<(), Box<dyn std::error::Error>> {

let provider = Arc::new(JsonRpcProvider::new("https://rpc.testnet.near.org"));

let args_json = json!({"account_id": "contract.near-api-rs.testnet"});
let args_json = json!({"account_id": "near-api-rs.testnet"});
let method_name = "get_status".to_string();

let result = view_function(provider, contract_id, method_name, args_json).await;
let result = view_function(provider, contract_id, method_name, args_json).await?;

println!("response: {:#?}", result);
println!("response single thread: {:#?}", String::from_utf8(result.result).expect("Failed to convert to String"));
println!("response log: {:#?}", result.logs);

Ok(())
}
Expand Down
5 changes: 4 additions & 1 deletion near-providers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ near-crypto = "0.21.1"
near-primitives = "0.21.1"
near-chain-configs = "0.21.1"
near-jsonrpc-primitives = "0.21.1"

reqwest = "0.12.3"
serde = "1.0.197"
serde_with = "3.8.1"
url = "2.2"

[dev-dependencies]
tokio = { version = "1", features = ["full", "test-util"] }
Expand Down
25 changes: 25 additions & 0 deletions near-providers/examples/access_key_fast_near.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use near_crypto::PublicKey;
use near_primitives::types::AccountId;
use near_primitives::views::QueryRequest;

use near_providers::FastNearHTTPClient;

mod utils;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();

let near_fast_client = FastNearHTTPClient::new("https://rpc.web4.near.page");
let account_id: AccountId = "vlad.near".parse::<AccountId>()?;
let public_key = "ed25519:JBHUrhF61wfScUxqGGRmfdJTQYg8MzRr5H8pqMMjqygr".parse::<PublicKey>()?;
let query_req = QueryRequest::ViewAccessKey {
account_id,
public_key,
};
let response = near_fast_client.access_key(query_req).await;

println!("response: {:#?}", response);

Ok(())
}
20 changes: 20 additions & 0 deletions near-providers/examples/account_info_fast_near.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use near_primitives::types::AccountId;
use near_primitives::views::QueryRequest;

use near_providers::FastNearHTTPClient;

mod utils;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();

let near_fast_client = FastNearHTTPClient::new("https://rpc.web4.testnet.page");
let account_id: AccountId = "contract.near-api-rs.testnet".parse::<AccountId>()?;
let query_req = QueryRequest::ViewAccount { account_id };
let response = near_fast_client.account_info(query_req).await;

println!("response: {:#?}", response);

Ok(())
}
20 changes: 20 additions & 0 deletions near-providers/examples/contract_methods_fast_near.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use near_primitives::types::AccountId;
use near_primitives::views::QueryRequest;

use near_providers::FastNearHTTPClient;

mod utils;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();

let near_fast_client = FastNearHTTPClient::new("https://rpc.web4.testnet.page");
let account_id: AccountId = "contract.near-api-rs.testnet".parse::<AccountId>()?;
let query_req = QueryRequest::ViewCode { account_id };
let response = near_fast_client.contract_methods(query_req).await;

println!("response: {:#?}", response);

Ok(())
}
49 changes: 49 additions & 0 deletions near-providers/examples/view_function_fast_near.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use near_primitives::types::AccountId;
use near_primitives::views::QueryRequest;

use near_providers::FastNearHTTPClient;
use serde_json::json;

mod utils;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();

let near_fast_client = FastNearHTTPClient::new("https://rpc.web4.testnet.page");
let account_id: AccountId = "contract.near-api-rs.testnet".parse::<AccountId>()?;

let args_json = json!({"account_id": "near-api-rs.testnet"});
let method_name = "get_status".to_string();
let args_vec = serde_json::to_vec(&args_json)?.into();

let query_req = QueryRequest::CallFunction {
account_id,
method_name,
args: args_vec,
};
let response = near_fast_client.view_function::<String>(query_req).await;

println!("response: {:#?}", response);

// Supporting json data type as input and output
let near_fast_client = FastNearHTTPClient::new("https://rpc.web4.near.page");
let account_id: AccountId = "lands.near".parse::<AccountId>()?;

let args_json = json!({"request.json": {"path":"/"}});
let method_name = "web4_get".to_string();
let args_vec = serde_json::to_vec(&args_json)?.into();

let query_req = QueryRequest::CallFunction {
account_id,
method_name,
args: args_vec,
};
let response = near_fast_client
.view_function::<serde_json::Value>(query_req)
.await?;

println!("response: {:#?}", response.to_string());

Ok(())
}
Empty file.
184 changes: 184 additions & 0 deletions near-providers/src/fast_near_http_provider.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
use std::error::Error;

use near_crypto::PublicKey;
use near_primitives::{
serialize::dec_format,
types::{Balance, Nonce},
views::{AccountView, QueryRequest},
};
use reqwest::{header, Client};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::Value;
use url::form_urlencoded;

#[derive(Clone)]
pub struct FastNearHTTPClient {
pub client: Client,
headers: header::HeaderMap,
server_addr: String,
}

#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)]
pub struct AccessKeyInfoView {
pub nonce: Nonce,
#[serde(with = "dec_format")]
pub allowance: Option<Balance>,
pub receiver_id: String,
pub method_names: Vec<String>,
pub public_key: PublicKey,
pub r#type: String,
}

pub trait ViewFunctionData: std::marker::Send + 'static {}

impl ViewFunctionData for String {}
impl ViewFunctionData for Vec<u8> {}
impl ViewFunctionData for serde_json::Value {}

trait QueryInfo {
fn url_fragment(&self, server_addr: &str) -> String;
}

impl QueryInfo for QueryRequest {
fn url_fragment(&self, server_addr: &str) -> String {
match self {
QueryRequest::ViewAccount { account_id } => {
format!("{}/account/{}", server_addr, account_id)
}
QueryRequest::ViewCode { account_id } => {
format!("{}/account/{}/contract/methods", server_addr, account_id)
}
QueryRequest::ViewAccessKey {
account_id,
public_key,
} => format!("{}/account/{}/key/{}", server_addr, account_id, public_key),
QueryRequest::CallFunction {
account_id,
method_name,
args,
} => {
// Convert the Vec<u8> into a String
let json_str =
String::from_utf8(args.clone().to_vec()).expect("Failed to convert to String");

// Parse the JSON string into a serde_json::Value
let parsed_json: Value =
serde_json::from_str(&json_str).expect("Failed to parse JSON");

let mut query_params = vec![];
// Check if the JSON value is an object and iterate over key-value pairs
if let Value::Object(map) = parsed_json {
for (key, value) in map {
// Convert the value to a string
let value_str = value.to_string();
// Remove quotes around strings if necessary
let value_str = if value_str.starts_with('"') && value_str.ends_with('"') {
&value_str[1..value_str.len() - 1]
} else {
&value_str
};
query_params.push((key, value_str.to_string()));
}
} else {
println!("The JSON value is not an object");
}

// Encode query parameters
let query_string: String = form_urlencoded::Serializer::new(String::new())
.extend_pairs(query_params)
.finish();
format!(
"{}/account/{}/view/{}?{}",
server_addr, account_id, method_name, query_string
)
}
_ => panic!("invalid QueryRequest"),
}
}
}

impl FastNearHTTPClient {
pub fn new(url: &str) -> Self {
let mut headers = reqwest::header::HeaderMap::with_capacity(2);
headers.insert(
reqwest::header::CONTENT_TYPE,
reqwest::header::HeaderValue::from_static("application/json"),
);

Self {
client: Client::new(),
headers: headers.clone(),
server_addr: url.to_owned(),
}
}

/// Get a shared reference to the headers.
pub fn headers(&self) -> &reqwest::header::HeaderMap {
&self.headers
}

/// Get an exclusive reference to the headers.
pub fn headers_mut(&mut self) -> &mut reqwest::header::HeaderMap {
&mut self.headers
}

async fn fetch_data<T: DeserializeOwned>(
&self,
query: QueryRequest,
) -> Result<T, Box<dyn Error>> {
let server_address = self.server_addr.clone();
let url = query.url_fragment(&server_address);
// let url = format!("{server_address}{url_fragment}");
println!("url {:#?}", url);

let request = self.client.get(url).headers(self.headers.clone());
let response = request.send().await?;

match response.status() {
reqwest::StatusCode::OK => {
println!("API call is success")
}
non_status_ok => {
let err_str = format!("API call failed with status code {non_status_ok}");
return Err(err_str.into());
}
}

let result = response.json::<T>().await;
match result {
Ok(result) => Ok(result),
Err(err) => Err(Box::new(err)),
}
}

pub async fn account_info(
&self,
query: QueryRequest,
) -> Result<AccountView, Box<dyn std::error::Error>> {
self.fetch_data::<AccountView>(query).await
}

pub async fn contract_methods(
&self,
query: QueryRequest,
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
self.fetch_data::<Vec<String>>(query).await
}

pub async fn access_key(
&self,
query: QueryRequest,
) -> Result<AccessKeyInfoView, Box<dyn std::error::Error>> {
self.fetch_data::<AccessKeyInfoView>(query).await
}

pub async fn view_function<T>(
&self,
query: QueryRequest,
) -> Result<T, Box<dyn std::error::Error>>
where
T: ViewFunctionData + serde::de::DeserializeOwned,
{
self.fetch_data::<T>(query).await
}
}
3 changes: 3 additions & 0 deletions near-providers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
//! This crate is designed to be easily extendable with more providers and to offer a
//! straightforward way to integrate NEAR blockchain functionalities into Rust applications.

/// Re-export the Provider trait
pub use crate::fast_near_http_provider::{AccessKeyInfoView, FastNearHTTPClient};
/// Re-export the JsonRpcProvider
pub use crate::json_rpc_provider::JsonRpcProvider;
/// Re-export the Provider trait
Expand All @@ -25,5 +27,6 @@ pub use near_jsonrpc_primitives::types;
pub use near_jsonrpc_client as jsonrpc_client;
pub use near_jsonrpc_primitives as jsonrpc_primitives;

mod fast_near_http_provider;
mod json_rpc_provider;
mod provider;

0 comments on commit 5bce33f

Please sign in to comment.