unified http api client
This commit is contained in:
Generated
+14
-16
@@ -3862,6 +3862,17 @@ dependencies = [
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-api-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.5"
|
||||
@@ -5550,13 +5561,14 @@ dependencies = [
|
||||
name = "mix-fetch-wasm"
|
||||
version = "1.2.0-rc.10"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures",
|
||||
"http-api-client",
|
||||
"js-sys",
|
||||
"nym-ordered-buffer",
|
||||
"nym-service-providers-common",
|
||||
"nym-socks5-requests",
|
||||
"rand 0.7.3",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"thiserror",
|
||||
@@ -6613,21 +6625,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-http-requests"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytecodec",
|
||||
"bytes",
|
||||
"http",
|
||||
"httpcodec",
|
||||
"nym-ordered-buffer",
|
||||
"nym-service-providers-common",
|
||||
"nym-socks5-requests",
|
||||
"thiserror",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-inclusion-probability"
|
||||
version = "0.1.0"
|
||||
@@ -7401,6 +7398,7 @@ dependencies = [
|
||||
"eyre",
|
||||
"flate2",
|
||||
"futures",
|
||||
"http-api-client",
|
||||
"itertools",
|
||||
"log",
|
||||
"nym-api-requests",
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ members = [
|
||||
"common/crypto",
|
||||
"common/dkg",
|
||||
"common/execute",
|
||||
"common/http-requests",
|
||||
"common/http-api-client",
|
||||
"common/inclusion-probability",
|
||||
"common/ledger",
|
||||
"common/mixnode-common",
|
||||
|
||||
@@ -24,6 +24,7 @@ nym-service-provider-directory-common = { path = "../../cosmwasm-smart-contracts
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
http-api-client = { path = "../../../common/http-api-client"}
|
||||
thiserror = { workspace = true }
|
||||
log = { workspace = true }
|
||||
url = { workspace = true, features = ["serde"] }
|
||||
|
||||
@@ -19,6 +19,7 @@ use nym_api_requests::models::{
|
||||
use nym_network_defaults::NymNetworkDetails;
|
||||
use url::Url;
|
||||
|
||||
pub use crate::nym_api::NymApiClientExt;
|
||||
pub use nym_mixnet_contract_common::{
|
||||
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, MixId,
|
||||
};
|
||||
@@ -177,7 +178,7 @@ impl<C, S> Client<C, S> {
|
||||
}
|
||||
|
||||
pub fn change_nym_api(&mut self, new_endpoint: Url) {
|
||||
self.nym_api.change_url(new_endpoint)
|
||||
self.nym_api.change_base_url(new_endpoint)
|
||||
}
|
||||
|
||||
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
@@ -251,7 +252,7 @@ impl NymApiClient {
|
||||
}
|
||||
|
||||
pub fn change_nym_api(&mut self, new_endpoint: Url) {
|
||||
self.nym_api.change_url(new_endpoint);
|
||||
self.nym_api.change_base_url(new_endpoint);
|
||||
}
|
||||
|
||||
pub async fn get_cached_active_mixnodes(
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use http_api_client::HttpClientError;
|
||||
use nym_api_requests::models::RequestError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum NymAPIError {
|
||||
#[error("There was an issue with the REST request - {source}")]
|
||||
ReqwestClientError {
|
||||
#[from]
|
||||
source: reqwest::Error,
|
||||
},
|
||||
|
||||
#[error("Not found")]
|
||||
NotFound,
|
||||
|
||||
#[error("Request failed with error message - {0}")]
|
||||
GenericRequestFailure(String),
|
||||
|
||||
#[error("The nym API has failed to resolve our request. It returned status code {status} and additional error message: {}", error.message())]
|
||||
ApiRequestFailure { status: u16, error: RequestError },
|
||||
}
|
||||
pub type NymAPIError = HttpClientError<RequestError>;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
use crate::nym_api::error::NymAPIError;
|
||||
use crate::nym_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
|
||||
use async_trait::async_trait;
|
||||
use http_api_client::{ApiClient, NO_PARAMS};
|
||||
use nym_api_requests::coconut::{
|
||||
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
|
||||
};
|
||||
@@ -10,133 +12,29 @@ use nym_api_requests::models::{
|
||||
ComputeRewardEstParam, GatewayBondAnnotated, GatewayCoreStatusResponse,
|
||||
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse,
|
||||
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
|
||||
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RequestError, RewardEstimationResponse,
|
||||
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
|
||||
StakeSaturationResponse, UptimeResponse,
|
||||
};
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
|
||||
use nym_name_service_common::response::NamesListResponse;
|
||||
use nym_service_provider_directory_common::response::ServicesListResponse;
|
||||
use reqwest::{Response, StatusCode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
pub mod error;
|
||||
pub mod routes;
|
||||
|
||||
type PathSegments<'a> = &'a [&'a str];
|
||||
type Params<'a, K, V> = &'a [(K, V)];
|
||||
pub use http_api_client::Client;
|
||||
|
||||
const NO_PARAMS: Params<'_, &'_ str, &'_ str> = &[];
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Client {
|
||||
url: Url,
|
||||
reqwest_client: reqwest::Client,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(url: Url) -> Self {
|
||||
let reqwest_client = reqwest::Client::new();
|
||||
Self {
|
||||
url,
|
||||
reqwest_client,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change_url(&mut self, new_url: Url) {
|
||||
self.url = new_url
|
||||
}
|
||||
|
||||
pub fn current_url(&self) -> &Url {
|
||||
&self.url
|
||||
}
|
||||
|
||||
async fn send_get_request<K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<Response, NymAPIError>
|
||||
where
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let url = create_api_url(&self.url, path, params);
|
||||
Ok(self.reqwest_client.get(url).send().await?)
|
||||
}
|
||||
|
||||
async fn query_nym_api<T, K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<T, NymAPIError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let res = self.send_get_request(path, params).await?;
|
||||
if res.status().is_success() {
|
||||
Ok(res.json().await?)
|
||||
} else if res.status() == StatusCode::NOT_FOUND {
|
||||
Err(NymAPIError::NotFound)
|
||||
} else {
|
||||
Err(NymAPIError::GenericRequestFailure(res.text().await?))
|
||||
}
|
||||
}
|
||||
|
||||
// This works for endpoints returning Result<Json<T>, ErrorResponse>
|
||||
async fn query_nym_api_fallible<T, K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<T, NymAPIError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let res = self.send_get_request(path, params).await?;
|
||||
let status = res.status();
|
||||
if res.status().is_success() {
|
||||
Ok(res.json().await?)
|
||||
} else {
|
||||
let request_error: RequestError = res.json().await?;
|
||||
Err(NymAPIError::ApiRequestFailure {
|
||||
status: status.as_u16(),
|
||||
error: request_error,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn post_nym_api<B, T, K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<T, NymAPIError>
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let url = create_api_url(&self.url, path, params);
|
||||
let response = self.reqwest_client.post(url).json(json_body).send().await?;
|
||||
if response.status().is_success() {
|
||||
Ok(response.json().await?)
|
||||
} else {
|
||||
Err(NymAPIError::GenericRequestFailure(response.text().await?))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.query_nym_api(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS)
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait NymApiClientExt: ApiClient {
|
||||
async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.get_json(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
async fn get_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
@@ -148,8 +46,8 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_gateways_detailed(&self) -> Result<Vec<GatewayBondAnnotated>, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
async fn get_gateways_detailed(&self) -> Result<Vec<GatewayBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
@@ -161,10 +59,10 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnodes_detailed_unfiltered(
|
||||
async fn get_mixnodes_detailed_unfiltered(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
@@ -176,23 +74,21 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_gateways(&self) -> Result<Vec<GatewayBond>, NymAPIError> {
|
||||
self.query_nym_api(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS)
|
||||
async fn get_gateways(&self) -> Result<Vec<GatewayBond>, NymAPIError> {
|
||||
self.get_json(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_active_mixnodes_detailed(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
async fn get_active_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
@@ -205,19 +101,19 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::MIXNODES, routes::REWARDED],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_report(
|
||||
async fn get_mixnode_report(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
) -> Result<MixnodeStatusReportResponse, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
@@ -230,11 +126,11 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_gateway_report(
|
||||
async fn get_gateway_report(
|
||||
&self,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
) -> Result<GatewayStatusReportResponse, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
@@ -247,11 +143,11 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_history(
|
||||
async fn get_mixnode_history(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
) -> Result<MixnodeUptimeHistoryResponse, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
@@ -264,11 +160,11 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_gateway_history(
|
||||
async fn get_gateway_history(
|
||||
&self,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
) -> Result<GatewayUptimeHistoryResponse, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
@@ -281,10 +177,10 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_rewarded_mixnodes_detailed(
|
||||
async fn get_rewarded_mixnodes_detailed(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
@@ -297,13 +193,13 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_gateway_core_status_count(
|
||||
async fn get_gateway_core_status_count(
|
||||
&self,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
since: Option<i64>,
|
||||
) -> Result<GatewayCoreStatusResponse, NymAPIError> {
|
||||
if let Some(since) = since {
|
||||
self.query_nym_api(
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
@@ -315,7 +211,7 @@ impl Client {
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
self.query_nym_api(
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
@@ -328,13 +224,13 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_core_status_count(
|
||||
async fn get_mixnode_core_status_count(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
since: Option<i64>,
|
||||
) -> Result<MixnodeCoreStatusResponse, NymAPIError> {
|
||||
if let Some(since) = since {
|
||||
self.query_nym_api(
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
@@ -346,7 +242,7 @@ impl Client {
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
self.query_nym_api(
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
@@ -360,11 +256,11 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_status(
|
||||
async fn get_mixnode_status(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
) -> Result<MixnodeStatusResponse, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
@@ -377,11 +273,11 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_reward_estimation(
|
||||
async fn get_mixnode_reward_estimation(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
) -> Result<RewardEstimationResponse, NymAPIError> {
|
||||
self.query_nym_api_fallible(
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
@@ -394,12 +290,12 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn compute_mixnode_reward_estimation(
|
||||
async fn compute_mixnode_reward_estimation(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
request_body: &ComputeRewardEstParam,
|
||||
) -> Result<RewardEstimationResponse, NymAPIError> {
|
||||
self.post_nym_api(
|
||||
self.post_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
@@ -413,11 +309,11 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_stake_saturation(
|
||||
async fn get_mixnode_stake_saturation(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
) -> Result<StakeSaturationResponse, NymAPIError> {
|
||||
self.query_nym_api_fallible(
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
@@ -430,11 +326,11 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_inclusion_probability(
|
||||
async fn get_mixnode_inclusion_probability(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
) -> Result<InclusionProbabilityResponse, NymAPIError> {
|
||||
self.query_nym_api_fallible(
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
@@ -447,11 +343,8 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_avg_uptime(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
) -> Result<UptimeResponse, NymAPIError> {
|
||||
self.query_nym_api_fallible(
|
||||
async fn get_mixnode_avg_uptime(&self, mix_id: MixId) -> Result<UptimeResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
@@ -464,11 +357,11 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn blind_sign(
|
||||
async fn blind_sign(
|
||||
&self,
|
||||
request_body: &BlindSignRequestBody,
|
||||
) -> Result<BlindedSignatureResponse, NymAPIError> {
|
||||
self.post_nym_api(
|
||||
self.post_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::COCONUT_ROUTES,
|
||||
@@ -481,11 +374,11 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn verify_bandwidth_credential(
|
||||
async fn verify_bandwidth_credential(
|
||||
&self,
|
||||
request_body: &VerifyCredentialBody,
|
||||
) -> Result<VerifyCredentialResponse, NymAPIError> {
|
||||
self.post_nym_api(
|
||||
self.post_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::COCONUT_ROUTES,
|
||||
@@ -498,118 +391,20 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_service_providers(&self) -> Result<ServicesListResponse, NymAPIError> {
|
||||
async fn get_service_providers(&self) -> Result<ServicesListResponse, NymAPIError> {
|
||||
log::trace!("Getting service providers");
|
||||
self.query_nym_api(&[routes::API_VERSION, routes::SERVICE_PROVIDERS], NO_PARAMS)
|
||||
self.get_json(&[routes::API_VERSION, routes::SERVICE_PROVIDERS], NO_PARAMS)
|
||||
.await
|
||||
}
|
||||
|
||||
//pub async fn get_registered_names(&self) -> Result<Vec<NameEntry>, NymAPIError> {
|
||||
pub async fn get_registered_names(&self) -> Result<NamesListResponse, NymAPIError> {
|
||||
//async fn get_registered_names(&self) -> Result<Vec<NameEntry>, NymAPIError> {
|
||||
async fn get_registered_names(&self) -> Result<NamesListResponse, NymAPIError> {
|
||||
log::trace!("Getting registered names");
|
||||
self.query_nym_api(&[routes::API_VERSION, routes::REGISTERED_NAMES], NO_PARAMS)
|
||||
self.get_json(&[routes::API_VERSION, routes::REGISTERED_NAMES], NO_PARAMS)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
// utility function that should solve the double slash problem in validator API forever.
|
||||
fn create_api_url<K: AsRef<str>, V: AsRef<str>>(
|
||||
base: &Url,
|
||||
segments: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Url {
|
||||
let mut url = base.clone();
|
||||
let mut path_segments = url
|
||||
.path_segments_mut()
|
||||
.expect("provided validator url does not have a base!");
|
||||
for segment in segments {
|
||||
let segment = segment.strip_prefix('/').unwrap_or(segment);
|
||||
let segment = segment.strip_suffix('/').unwrap_or(segment);
|
||||
|
||||
path_segments.push(segment);
|
||||
}
|
||||
// I don't understand why compiler couldn't figure out that it's no longer used
|
||||
// and can be dropped
|
||||
drop(path_segments);
|
||||
|
||||
if !params.is_empty() {
|
||||
url.query_pairs_mut().extend_pairs(params);
|
||||
}
|
||||
|
||||
url
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn creating_api_path() {
|
||||
let base_url: Url = "http://foomp.com".parse().unwrap();
|
||||
|
||||
// works with 1 segment
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo",
|
||||
create_api_url(&base_url, &["foo"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with 2 segments
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
create_api_url(&base_url, &["foo", "bar"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with leading slash
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo",
|
||||
create_api_url(&base_url, &["/foo"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
create_api_url(&base_url, &["/foo", "bar"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
create_api_url(&base_url, &["foo", "/bar"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with trailing slash
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo",
|
||||
create_api_url(&base_url, &["foo/"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
create_api_url(&base_url, &["foo/", "bar"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
create_api_url(&base_url, &["foo", "bar/"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with both leading and trailing slash
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo",
|
||||
create_api_url(&base_url, &["/foo/"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
create_api_url(&base_url, &["/foo/", "/bar/"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// adds params
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar?foomp=baz",
|
||||
create_api_url(&base_url, &["foo", "bar"], &[("foomp", "baz")]).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar?arg1=val1&arg2=val2",
|
||||
create_api_url(
|
||||
&base_url,
|
||||
&["/foo/", "/bar/"],
|
||||
&[("arg1", "val1"), ("arg2", "val2")]
|
||||
)
|
||||
.as_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl NymApiClientExt for Client {}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use comfy_table::Table;
|
||||
|
||||
use crate::context::QueryClientWithNyxd;
|
||||
use crate::utils::{pretty_cosmwasm_coin, show_error};
|
||||
use clap::Parser;
|
||||
use comfy_table::Table;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use comfy_table::Table;
|
||||
|
||||
use crate::context::QueryClientWithNyxd;
|
||||
use crate::utils::{pretty_decimal_with_denom, show_error};
|
||||
use clap::Parser;
|
||||
use comfy_table::Table;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use comfy_table::Table;
|
||||
use nym_validator_client::nym_api::error::NymAPIError;
|
||||
|
||||
use crate::context::QueryClientWithNyxd;
|
||||
use crate::utils::show_error;
|
||||
use clap::Parser;
|
||||
use comfy_table::Table;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::nym_api::error::NymAPIError;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use comfy_table::Table;
|
||||
use nym_validator_client::nym_api::error::NymAPIError;
|
||||
|
||||
use crate::context::QueryClientWithNyxd;
|
||||
use crate::utils::show_error;
|
||||
use clap::Parser;
|
||||
use comfy_table::Table;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::nym_api::error::NymAPIError;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "http-api-client"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
@@ -0,0 +1,315 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use async_trait::async_trait;
|
||||
use reqwest::{IntoUrl, Response, StatusCode, Url};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
use thiserror::Error;
|
||||
|
||||
pub type PathSegments<'a> = &'a [&'a str];
|
||||
pub type Params<'a, K, V> = &'a [(K, V)];
|
||||
|
||||
pub const NO_PARAMS: Params<'_, &'_ str, &'_ str> = &[];
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum HttpClientError<E: Display = String> {
|
||||
#[error("there was an issue with the REST request: {source}")]
|
||||
ReqwestClientError {
|
||||
#[from]
|
||||
source: reqwest::Error,
|
||||
},
|
||||
|
||||
#[error("not found")]
|
||||
NotFound,
|
||||
|
||||
#[error("request failed with error message: {0}")]
|
||||
GenericRequestFailure(String),
|
||||
|
||||
#[error("failed to resolve request. status code: {status}, additional error message: {error}")]
|
||||
EndpointFailure { status: u16, error: E },
|
||||
}
|
||||
|
||||
/// A simple extendable client wrapper for http request with extra url sanitization.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Client {
|
||||
base_url: Url,
|
||||
reqwest_client: reqwest::Client,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(base_url: Url) -> Self {
|
||||
Client {
|
||||
base_url,
|
||||
reqwest_client: reqwest::Client::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_url<U, E>(url: U) -> Result<Self, HttpClientError<E>>
|
||||
where
|
||||
U: IntoUrl,
|
||||
E: Display,
|
||||
{
|
||||
Ok(Self::new(url.into_url()?))
|
||||
}
|
||||
|
||||
pub fn change_base_url(&mut self, new_url: Url) {
|
||||
self.base_url = new_url
|
||||
}
|
||||
|
||||
pub fn current_url(&self) -> &Url {
|
||||
&self.base_url
|
||||
}
|
||||
|
||||
async fn send_get_request<K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
E: Display,
|
||||
{
|
||||
let url = sanitize_url(&self.base_url, path, params);
|
||||
Ok(self.reqwest_client.get(url).send().await?)
|
||||
}
|
||||
|
||||
async fn send_post_request<B, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
E: Display,
|
||||
{
|
||||
let url = sanitize_url(&self.base_url, path, params);
|
||||
Ok(self.reqwest_client.post(url).json(json_body).send().await?)
|
||||
}
|
||||
|
||||
pub async fn get_json_endpoint<T, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
E: Display + DeserializeOwned,
|
||||
{
|
||||
let res = self.send_get_request(path, params).await?;
|
||||
parse_response(res).await
|
||||
}
|
||||
|
||||
pub async fn post_json_endpoint<B, T, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
E: Display + DeserializeOwned,
|
||||
{
|
||||
let res = self.send_post_request(path, params, json_body).await?;
|
||||
parse_response(res).await
|
||||
}
|
||||
}
|
||||
|
||||
// define those methods on the trait for nicer extensions (and not having to type the thing twice)
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait ApiClient {
|
||||
async fn get_json<T, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display + DeserializeOwned;
|
||||
|
||||
async fn post_json<B, T, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display + DeserializeOwned;
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl ApiClient for Client {
|
||||
async fn get_json<T, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display + DeserializeOwned,
|
||||
{
|
||||
self.get_json_endpoint(path, params).await
|
||||
}
|
||||
|
||||
async fn post_json<B, T, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display + DeserializeOwned,
|
||||
{
|
||||
self.post_json_endpoint(path, params, json_body).await
|
||||
}
|
||||
}
|
||||
|
||||
// utility function that should solve the double slash problem in API urls forever.
|
||||
pub fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
|
||||
base: &Url,
|
||||
segments: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Url {
|
||||
let mut url = base.clone();
|
||||
let mut path_segments = url
|
||||
.path_segments_mut()
|
||||
.expect("provided validator url does not have a base!");
|
||||
for segment in segments {
|
||||
let segment = segment.strip_prefix('/').unwrap_or(segment);
|
||||
let segment = segment.strip_suffix('/').unwrap_or(segment);
|
||||
|
||||
path_segments.push(segment);
|
||||
}
|
||||
// I don't understand why compiler couldn't figure out that it's no longer used
|
||||
// and can be dropped
|
||||
drop(path_segments);
|
||||
|
||||
if !params.is_empty() {
|
||||
url.query_pairs_mut().extend_pairs(params);
|
||||
}
|
||||
|
||||
url
|
||||
}
|
||||
|
||||
async fn parse_response<T, E>(res: Response) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
E: DeserializeOwned + Display,
|
||||
{
|
||||
let status = res.status();
|
||||
|
||||
if res.status().is_success() {
|
||||
Ok(res.json().await?)
|
||||
} else if res.status() == StatusCode::NOT_FOUND {
|
||||
Err(HttpClientError::NotFound)
|
||||
} else {
|
||||
let plaintext = res.text().await?;
|
||||
if let Ok(request_error) = serde_json::from_str(&plaintext) {
|
||||
Err(HttpClientError::EndpointFailure {
|
||||
status: status.as_u16(),
|
||||
error: request_error,
|
||||
})
|
||||
} else {
|
||||
Err(HttpClientError::GenericRequestFailure(plaintext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sanitizing_urls() {
|
||||
let base_url: Url = "http://foomp.com".parse().unwrap();
|
||||
|
||||
// works with 1 segment
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo",
|
||||
sanitize_url(&base_url, &["foo"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with 2 segments
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
sanitize_url(&base_url, &["foo", "bar"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with leading slash
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo",
|
||||
sanitize_url(&base_url, &["/foo"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
sanitize_url(&base_url, &["/foo", "bar"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
sanitize_url(&base_url, &["foo", "/bar"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with trailing slash
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo",
|
||||
sanitize_url(&base_url, &["foo/"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
sanitize_url(&base_url, &["foo/", "bar"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
sanitize_url(&base_url, &["foo", "bar/"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with both leading and trailing slash
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo",
|
||||
sanitize_url(&base_url, &["/foo/"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
sanitize_url(&base_url, &["/foo/", "/bar/"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// adds params
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar?foomp=baz",
|
||||
sanitize_url(&base_url, &["foo", "bar"], &[("foomp", "baz")]).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar?arg1=val1&arg2=val2",
|
||||
sanitize_url(
|
||||
&base_url,
|
||||
&["/foo/", "/bar/"],
|
||||
&[("arg1", "val1"), ("arg2", "val2")]
|
||||
)
|
||||
.as_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
[package]
|
||||
name = "nym-http-requests"
|
||||
version = "0.1.0"
|
||||
description = "Helper library for sending HTTP requesters over the Nym mixnet"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
license = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
nym-socks5-requests = { path = "../socks5/requests" }
|
||||
nym-ordered-buffer = { path = "../socks5/ordered-buffer" }
|
||||
nym-service-providers-common = { path = "../../service-providers/common" }
|
||||
bytecodec = "0.4.15"
|
||||
httpcodec = "0.2.3"
|
||||
bytes = "1"
|
||||
http = "0.2.9"
|
||||
thiserror = { workspace = true }
|
||||
url = "2"
|
||||
@@ -1,23 +0,0 @@
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MixHttpRequestError {
|
||||
#[error("invalid Socks5 response")]
|
||||
InvalidSocks5Response,
|
||||
|
||||
#[error("the received Socks5 response was empty")]
|
||||
EmptySocks5Response,
|
||||
|
||||
#[error("bytecodec Error: {0}")]
|
||||
ByteCodecError(#[from] bytecodec::Error),
|
||||
|
||||
#[error("Url parse error: {0}")]
|
||||
UrlParseError(#[from] url::ParseError),
|
||||
|
||||
#[error("could not resolve socket address from the provided URL")]
|
||||
SocketAddrResolveError {
|
||||
#[source]
|
||||
source: io::Error,
|
||||
},
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bytecodec::bytes::BytesEncoder;
|
||||
use bytecodec::io::IoEncodeExt;
|
||||
use bytecodec::Encode;
|
||||
use httpcodec::{BodyEncoder, Request, RequestEncoder};
|
||||
|
||||
pub mod error;
|
||||
pub mod socks;
|
||||
|
||||
pub fn encode_http_request_as_string(
|
||||
request: Request<Vec<u8>>,
|
||||
) -> Result<String, error::MixHttpRequestError> {
|
||||
// Encode HTTP request as bytes
|
||||
let mut encoder = RequestEncoder::new(BodyEncoder::new(BytesEncoder::new()));
|
||||
encoder.start_encoding(request)?;
|
||||
let mut buf = Vec::new();
|
||||
encoder.encode_all(&mut buf)?;
|
||||
|
||||
Ok(String::from_utf8_lossy(&buf).to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod http_requests_tests {
|
||||
use super::*;
|
||||
use httpcodec::{HeaderField, HttpVersion, Method, RequestTarget};
|
||||
|
||||
fn create_http_get_request() -> Request<Vec<u8>> {
|
||||
let mut request = Request::new(
|
||||
Method::new("GET").unwrap(),
|
||||
RequestTarget::new("/.wellknown/wallet/validators.json").unwrap(),
|
||||
HttpVersion::V1_1,
|
||||
b"".to_vec(),
|
||||
);
|
||||
let mut headers = request.header_mut();
|
||||
headers.add_field(HeaderField::new("Host", "nymtech.net").unwrap());
|
||||
|
||||
request
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_request_ok() {
|
||||
// Encode HTTP request as bytes
|
||||
let request = create_http_get_request();
|
||||
let mut encoder = RequestEncoder::new(BodyEncoder::new(BytesEncoder::new()));
|
||||
encoder.start_encoding(request).unwrap();
|
||||
let mut buf = Vec::new();
|
||||
encoder.encode_all(&mut buf).unwrap();
|
||||
|
||||
let body_as_string = String::from_utf8(buf).unwrap();
|
||||
|
||||
// replace newlines with \r\n
|
||||
let expected = r"GET /.wellknown/wallet/validators.json HTTP/1.1
|
||||
Host: nymtech.net
|
||||
Content-Length: 0
|
||||
|
||||
"
|
||||
.replace('\n', "\r\n");
|
||||
|
||||
assert_eq!(expected, body_as_string);
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error;
|
||||
use bytecodec::bytes::BytesEncoder;
|
||||
use bytecodec::bytes::RemainingBytesDecoder;
|
||||
use bytecodec::io::IoEncodeExt;
|
||||
use bytecodec::{DecodeExt, Encode};
|
||||
use httpcodec::{BodyDecoder, ResponseDecoder};
|
||||
use httpcodec::{BodyEncoder, Request, RequestEncoder};
|
||||
use nym_service_providers_common::interface::ProviderInterfaceVersion;
|
||||
use nym_socks5_requests::{SocketData, Socks5ProtocolVersion, Socks5ProviderRequest};
|
||||
|
||||
pub fn encode_http_request_as_socks_send_request(
|
||||
provider_interface: ProviderInterfaceVersion,
|
||||
socks5_protocol: Socks5ProtocolVersion,
|
||||
conn_id: u64,
|
||||
request: Request<Vec<u8>>,
|
||||
seq: Option<u64>,
|
||||
local_closed: bool,
|
||||
) -> Result<nym_socks5_requests::Socks5ProviderRequest, error::MixHttpRequestError> {
|
||||
// Encode HTTP request as bytes
|
||||
let mut encoder = RequestEncoder::new(BodyEncoder::new(BytesEncoder::new()));
|
||||
encoder.start_encoding(request)?;
|
||||
let mut buf = Vec::new();
|
||||
encoder.encode_all(&mut buf)?;
|
||||
|
||||
// Wrap it as SOCKS send request
|
||||
let request_content = nym_socks5_requests::request::Socks5Request::new_send(
|
||||
socks5_protocol,
|
||||
SocketData::new(seq.unwrap_or_default(), conn_id, local_closed, buf),
|
||||
);
|
||||
|
||||
// and wrap it in provider request
|
||||
Ok(Socks5ProviderRequest::new_provider_data(
|
||||
provider_interface,
|
||||
request_content,
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MixHttpResponse {
|
||||
// pub connection_id: u64,
|
||||
// #[deprecated]
|
||||
// pub is_closed: bool,
|
||||
pub http_response: httpcodec::Response<Vec<u8>>,
|
||||
// #[deprecated]
|
||||
// pub seq: u64,
|
||||
}
|
||||
|
||||
impl MixHttpResponse {
|
||||
pub fn try_from_bytes(b: &[u8]) -> Result<MixHttpResponse, error::MixHttpRequestError> {
|
||||
if b.is_empty() {
|
||||
Err(error::MixHttpRequestError::EmptySocks5Response)
|
||||
} else {
|
||||
let mut decoder = ResponseDecoder::<BodyDecoder<RemainingBytesDecoder>>::default();
|
||||
let http_response = decoder.decode_from_bytes(b)?;
|
||||
|
||||
Ok(MixHttpResponse { http_response })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// impl TryFrom<Socks5Response> for MixHttpResponse {
|
||||
// type Error = error::MixHttpRequestError;
|
||||
//
|
||||
// fn try_from(value: Socks5Response) -> Result<Self, Self::Error> {
|
||||
// if let Socks5ResponseContent::NetworkData { content } = value.content {
|
||||
// content.try_into()
|
||||
// } else {
|
||||
// Err(error::MixHttpRequestError::InvalidSocks5Response)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl TryFrom<SocketData> for MixHttpResponse {
|
||||
// type Error = error::MixHttpRequestError;
|
||||
//
|
||||
// fn try_from(value: SocketData) -> Result<Self, Self::Error> {
|
||||
// if value.data.is_empty() {
|
||||
// Err(error::MixHttpRequestError::EmptySocks5Response)
|
||||
// } else {
|
||||
// let mut decoder = ResponseDecoder::<BodyDecoder<RemainingBytesDecoder>>::default();
|
||||
// let http_response = decoder.decode_from_bytes(value.data.as_ref())?;
|
||||
//
|
||||
// Ok(MixHttpResponse {
|
||||
// connection_id: value.header.connection_id,
|
||||
// is_closed: value.header.local_socket_closed,
|
||||
// http_response,
|
||||
// seq: value.header.seq,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn decode_socks_response_as_http_response(
|
||||
// socks5_response: Socks5Response,
|
||||
// ) -> Result<MixHttpResponse, error::MixHttpRequestError> {
|
||||
// socks5_response.try_into()
|
||||
// }
|
||||
//
|
||||
// #[cfg(test)]
|
||||
// mod http_requests_tests {
|
||||
// use super::*;
|
||||
// use httpcodec::{HeaderField, HttpVersion, Method, RequestTarget};
|
||||
// use nym_service_providers_common::interface::Serializable;
|
||||
// use nym_socks5_requests::Socks5Response;
|
||||
//
|
||||
// fn create_http_get_request() -> Request<Vec<u8>> {
|
||||
// let mut request = Request::new(
|
||||
// Method::new("GET").unwrap(),
|
||||
// RequestTarget::new("/.wellknown/wallet/validators.json").unwrap(),
|
||||
// HttpVersion::V1_1,
|
||||
// b"".to_vec(),
|
||||
// );
|
||||
// let mut headers = request.header_mut();
|
||||
// headers.add_field(HeaderField::new("Host", "nymtech.net").unwrap());
|
||||
//
|
||||
// request
|
||||
// }
|
||||
//
|
||||
// fn create_socks5_request_buffer() -> Vec<u8> {
|
||||
// let request = create_http_get_request();
|
||||
// let socks5_request = encode_http_request_as_socks_send_request(
|
||||
// ProviderInterfaceVersion::new_current(),
|
||||
// Socks5ProtocolVersion::new_current(),
|
||||
// 99u64,
|
||||
// request,
|
||||
// Some(42u64),
|
||||
// true,
|
||||
// )
|
||||
// .unwrap();
|
||||
// socks5_request.into_bytes()
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn request_http_request_content_ok() {
|
||||
// let buffer = create_socks5_request_buffer();
|
||||
//
|
||||
// // HTTP request string content is as expected
|
||||
// assert_eq!(
|
||||
// [71u8, 69u8, 84u8, 32u8, 47u8, 46u8, 119u8, 101u8],
|
||||
// buffer[19..27]
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// /// This test will fail if the framing of the request buffer changes, e.g. when OrderedMessage
|
||||
// /// changes to have the `index` value as a field, instead of packed with the `data`
|
||||
// #[test]
|
||||
// fn request_size_as_expected_ok() {
|
||||
// let buffer = create_socks5_request_buffer();
|
||||
// // println!("{:?}", buffer) // uncomment and run `cargo test -- --nocapture` to view
|
||||
//
|
||||
// assert_eq!(108, buffer.len()); // version set to SOCKS5
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn request_socks5_headers_ok() {
|
||||
// let buffer = create_socks5_request_buffer();
|
||||
//
|
||||
// assert_eq!(5u8, buffer[0]); // version set to SOCKS5
|
||||
// assert_eq!(1u8, buffer[1]); // type is SEND
|
||||
// assert_eq!(99u8, buffer[9]); // ConnectionId is correct
|
||||
// assert_eq!(1u8, buffer[10]); // local_closed is true
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn request_ordered_message_ok() {
|
||||
// let buffer = create_socks5_request_buffer();
|
||||
//
|
||||
// // OrderedMessage index is correct
|
||||
// assert_eq!(42u8, buffer[18]);
|
||||
// }
|
||||
//
|
||||
// fn create_socks_response() -> Socks5Response {
|
||||
// // HTTP response is just a string
|
||||
// let http_response_string = "HTTP/1.1 200 OK\r\nServer: foo/0.0.1\r\n\r\n";
|
||||
//
|
||||
// let data = http_response_string.as_bytes().to_vec();
|
||||
//
|
||||
// // wrap in `NetworkData`, then Socks5Response
|
||||
// Socks5Response::new(
|
||||
// Socks5ProtocolVersion::new_current(),
|
||||
// Socks5ResponseContent::NetworkData {
|
||||
// content: SocketData::new(42, 99u64, false, data),
|
||||
// },
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// /// This test will fail is anything in the framing of the socks5_response byte
|
||||
// /// representation changes
|
||||
// #[test]
|
||||
// fn response_byte_size_is_as_expected() {
|
||||
// let socks5_response = create_socks_response();
|
||||
// let buf = socks5_response.into_bytes();
|
||||
//
|
||||
// assert_eq!(57, buf.len());
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn response_parses() {
|
||||
// unimplemented!()
|
||||
// // let socks5_response = create_socks_response();
|
||||
// // let response = decode_socks_response_as_http_response(socks5_response).unwrap();
|
||||
// //
|
||||
// // assert_eq!(42u64, response.seq); // OrderedMessage index as expected
|
||||
// // assert_eq!(HttpVersion::V1_1, response.http_response.http_version());
|
||||
// // assert_eq!(200u16, response.http_response.status_code().as_u16());
|
||||
// // assert_eq!(
|
||||
// // "foo/0.0.1",
|
||||
// // response.http_response.header().get_field("Server").unwrap()
|
||||
// // );
|
||||
// }
|
||||
// }
|
||||
@@ -6,6 +6,7 @@ use crate::helpers::best_effort_small_dec_to_f64;
|
||||
use crate::mix_node::models::EconomicDynamicsStats;
|
||||
use nym_contracts_common::truncate_decimal;
|
||||
use nym_mixnet_contract_common::MixId;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
|
||||
pub(crate) async fn retrieve_mixnode_econ_stats(
|
||||
client: &ThreadsafeValidatorClient,
|
||||
|
||||
@@ -11,6 +11,7 @@ use nym_mixnet_contract_common::{
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::{fmt, time::Duration};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
@@ -30,6 +31,12 @@ impl RequestError {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RequestError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.message.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
|
||||
Generated
+12
@@ -2964,6 +2964,17 @@ dependencies = [
|
||||
"itoa 1.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-api-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.5"
|
||||
@@ -4634,6 +4645,7 @@ dependencies = [
|
||||
"eyre",
|
||||
"flate2",
|
||||
"futures",
|
||||
"http-api-client",
|
||||
"itertools",
|
||||
"log",
|
||||
"nym-api-requests",
|
||||
|
||||
@@ -8,6 +8,7 @@ use nym_bin_common::version_checker::is_minor_version_compatible;
|
||||
use nym_config::defaults::var_names::NYM_API;
|
||||
use nym_contracts_common::types::Percent;
|
||||
use nym_topology::gateway;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::nym_api::Client as ApiClient;
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub struct Client {
|
||||
//
|
||||
}
|
||||
@@ -7,8 +7,13 @@ use nym_crypto::asymmetric::identity;
|
||||
use serde::Serialize;
|
||||
use std::ops::Deref;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
pub mod client;
|
||||
pub mod v1;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
pub use client::Client;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
#[cfg_attr(feature = "openapi", aliases(SignedHostInformation = SignedData<HostInformation>))]
|
||||
|
||||
Generated
+12
@@ -2390,6 +2390,17 @@ dependencies = [
|
||||
"itoa 1.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-api-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.5"
|
||||
@@ -3434,6 +3445,7 @@ dependencies = [
|
||||
"eyre",
|
||||
"flate2",
|
||||
"futures",
|
||||
"http-api-client",
|
||||
"itertools",
|
||||
"log",
|
||||
"nym-api-requests",
|
||||
|
||||
@@ -14,6 +14,7 @@ use nym_types::currency::DecCoin;
|
||||
use nym_types::gateway::GatewayBond;
|
||||
use nym_types::mixnode::{MixNodeCostParams, MixNodeDetails};
|
||||
use nym_types::transaction::TransactionExecuteResult;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient};
|
||||
use nym_validator_client::nyxd::Fee;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -13,6 +13,7 @@ use nym_types::deprecated::{
|
||||
use nym_types::mixnode::MixNodeCostParams;
|
||||
use nym_types::pending_events::PendingEpochEvent;
|
||||
use nym_types::transaction::TransactionExecuteResult;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::nyxd::contract_traits::{
|
||||
MixnetQueryClient, MixnetSigningClient, NymContractsProvider, PagedMixnetQueryClient,
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::state::WalletState;
|
||||
use nym_mixnet_contract_common::{
|
||||
reward_params::Performance, Coin, IdentityKeyRef, MixId, Percent,
|
||||
};
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::models::{
|
||||
ComputeRewardEstParam, GatewayCoreStatusResponse, GatewayStatusReportResponse,
|
||||
InclusionProbabilityResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
|
||||
|
||||
@@ -14,10 +14,10 @@ rust-version = "1.70"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
js-sys = { workspace = true }
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
reqwest = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde-wasm-bindgen = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
@@ -27,6 +27,7 @@ wasm-bindgen-futures = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tsify = { workspace = true, features = ["js"] }
|
||||
|
||||
http-api-client = { path = "../../common/http-api-client" }
|
||||
nym-socks5-requests = { path = "../../common/socks5/requests" }
|
||||
nym-ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
|
||||
nym-service-providers-common = { path = "../../service-providers/common" }
|
||||
|
||||
Binary file not shown.
@@ -4,10 +4,13 @@
|
||||
// this exists inside mix-fetch rather than being made into repo-wide library since it's a temporary solution
|
||||
// until the API is finalised and endpoints are moved to nym-api
|
||||
|
||||
use reqwest::{IntoUrl, Response, StatusCode};
|
||||
use async_trait::async_trait;
|
||||
use http_api_client::{ApiClient, HttpClientError, NO_PARAMS};
|
||||
use serde::Deserialize;
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
pub use http_api_client::Client;
|
||||
|
||||
pub type HarbourMasterApiError = HttpClientError;
|
||||
|
||||
mod routes {
|
||||
pub const API_VERSION: &str = "v1";
|
||||
@@ -17,79 +20,12 @@ mod routes {
|
||||
pub const NEW: &str = "new";
|
||||
}
|
||||
|
||||
// most of it is copied from the nym-api client
|
||||
type PathSegments<'a> = &'a [&'a str];
|
||||
type Params<'a, K, V> = &'a [(K, V)];
|
||||
|
||||
const NO_PARAMS: Params<'_, &'_ str, &'_ str> = &[];
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum HarbourMasterApiError {
|
||||
#[error("there was an issue with the REST request: {source}")]
|
||||
ReqwestClientError {
|
||||
#[from]
|
||||
source: reqwest::Error,
|
||||
},
|
||||
|
||||
#[error("not found")]
|
||||
NotFound,
|
||||
|
||||
#[error("request failed with error message: {0}")]
|
||||
GenericRequestFailure(String),
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
url: Url,
|
||||
reqwest_client: reqwest::Client,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new<U: IntoUrl>(url: U) -> Result<Self, HarbourMasterApiError> {
|
||||
let reqwest_client = reqwest::Client::new();
|
||||
Ok(Self {
|
||||
url: url.into_url()?,
|
||||
reqwest_client,
|
||||
})
|
||||
}
|
||||
|
||||
async fn send_get_request<K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<Response, HarbourMasterApiError>
|
||||
where
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let url = create_api_url(&self.url, path, params);
|
||||
Ok(self.reqwest_client.get(url).send().await?)
|
||||
}
|
||||
|
||||
async fn query_harbourmaster<T, K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<T, HarbourMasterApiError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let res = self.send_get_request(path, params).await?;
|
||||
if res.status().is_success() {
|
||||
Ok(res.json().await?)
|
||||
} else if res.status() == StatusCode::NOT_FOUND {
|
||||
Err(HarbourMasterApiError::NotFound)
|
||||
} else {
|
||||
Err(HarbourMasterApiError::GenericRequestFailure(
|
||||
res.text().await?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait HarbourMasterApiClientExt: ApiClient {
|
||||
// since it's a temporary thing don't worry about paging.
|
||||
pub async fn get_services_new(&self) -> Result<PagedResult<ServiceNew>, HarbourMasterApiError> {
|
||||
self.query_harbourmaster(
|
||||
async fn get_services_new(&self) -> Result<PagedResult<ServiceNew>, HarbourMasterApiError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::SERVICES, routes::NEW],
|
||||
NO_PARAMS,
|
||||
)
|
||||
@@ -97,29 +33,9 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_api_url<K: AsRef<str>, V: AsRef<str>>(
|
||||
base: &Url,
|
||||
segments: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Url {
|
||||
let mut url = base.clone();
|
||||
let mut path_segments = url
|
||||
.path_segments_mut()
|
||||
.expect("provided validator url does not have a base!");
|
||||
for segment in segments {
|
||||
let segment = segment.strip_prefix('/').unwrap_or(segment);
|
||||
let segment = segment.strip_suffix('/').unwrap_or(segment);
|
||||
|
||||
path_segments.push(segment);
|
||||
}
|
||||
drop(path_segments);
|
||||
|
||||
if !params.is_empty() {
|
||||
url.query_pairs_mut().extend_pairs(params);
|
||||
}
|
||||
|
||||
url
|
||||
}
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl HarbourMasterApiClientExt for Client {}
|
||||
|
||||
// https://gitlab.nymte.ch/nym/shipyard-test-and-earn/-/blob/main/harbour-master/src/http/mod.rs#L13
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::error::MixFetchError;
|
||||
use crate::harbourmaster;
|
||||
use crate::harbourmaster::HarbourMasterApiClientExt;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::collections::HashMap;
|
||||
@@ -23,7 +24,7 @@ pub(crate) async fn get_network_requester(
|
||||
return Ok(sp);
|
||||
}
|
||||
|
||||
let client = harbourmaster::Client::new(HARBOUR_MASTER)?;
|
||||
let client = harbourmaster::Client::new_url(HARBOUR_MASTER)?;
|
||||
let providers = client.get_services_new().await?;
|
||||
console_log!(
|
||||
"obtained list of {} service providers on the network",
|
||||
|
||||
Reference in New Issue
Block a user