-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
funds-manager: execution-client: Add quote and price helpers
- Loading branch information
Showing
4 changed files
with
187 additions
and
0 deletions.
There are no files selected for viewing
41 changes: 41 additions & 0 deletions
41
funds-manager/funds-manager-server/src/execution_client/error.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
//! Error types for the execution client | ||
|
||
use std::fmt::Display; | ||
|
||
/// An error returned by the execution client | ||
#[derive(Debug, Clone)] | ||
pub enum ExecutionClientError { | ||
/// An error returned by the execution client | ||
Http(String), | ||
/// An error parsing a value | ||
Parse(String), | ||
} | ||
|
||
impl ExecutionClientError { | ||
/// Create a new http error | ||
pub fn http<T: ToString>(e: T) -> Self { | ||
Check failure on line 16 in funds-manager/funds-manager-server/src/execution_client/error.rs GitHub Actions / clippythis argument is passed by value, but not consumed in the function body
|
||
ExecutionClientError::Http(e.to_string()) | ||
} | ||
|
||
/// Create a new parse error | ||
pub fn parse<T: ToString>(e: T) -> Self { | ||
Check failure on line 21 in funds-manager/funds-manager-server/src/execution_client/error.rs GitHub Actions / clippythis argument is passed by value, but not consumed in the function body
|
||
ExecutionClientError::Parse(e.to_string()) | ||
} | ||
} | ||
|
||
impl Display for ExecutionClientError { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
let msg = match self { | ||
ExecutionClientError::Http(e) => format!("HTTP error: {e}"), | ||
ExecutionClientError::Parse(e) => format!("Parse error: {e}"), | ||
}; | ||
|
||
write!(f, "{}", msg) | ||
} | ||
} | ||
|
||
impl From<reqwest::Error> for ExecutionClientError { | ||
fn from(e: reqwest::Error) -> Self { | ||
ExecutionClientError::http(e) | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
funds-manager/funds-manager-server/src/execution_client/mod.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
//! Client for interacting with execution venues, currently this is 0x Matcha | ||
pub mod error; | ||
pub mod quotes; | ||
|
||
use std::sync::Arc; | ||
|
||
use reqwest::{Client, Url}; | ||
use serde::Deserialize; | ||
|
||
use self::error::ExecutionClientError; | ||
|
||
/// The 0x api key header | ||
const API_KEY_HEADER: &str = "0x-api-key"; | ||
|
||
/// The client for interacting with the execution venue | ||
#[derive(Clone)] | ||
pub struct ExecutionClient { | ||
/// The API key to use for requests | ||
api_key: String, | ||
/// The base URL for the execution client | ||
base_url: String, | ||
/// The underlying HTTP client | ||
http_client: Arc<Client>, | ||
} | ||
|
||
impl ExecutionClient { | ||
/// Create a new client | ||
pub fn new(api_key: String, base_url: String) -> Self { | ||
Self { api_key, base_url, http_client: Arc::new(Client::new()) } | ||
} | ||
|
||
/// Get a full URL for a given endpoint | ||
fn build_url( | ||
&self, | ||
endpoint: &str, | ||
params: &[(&str, &str)], | ||
) -> Result<Url, ExecutionClientError> { | ||
let endpoint = if !endpoint.starts_with('/') { | ||
format!("{}/{}", self.base_url, endpoint) | ||
} else { | ||
format!("{}{}", self.base_url, endpoint) | ||
}; | ||
println!("endpoint: {}", endpoint); | ||
|
||
Url::parse_with_params(&endpoint, params).map_err(ExecutionClientError::parse) | ||
} | ||
|
||
/// Send a get request to the execution venue | ||
async fn get_execution<T: for<'de> Deserialize<'de>>( | ||
&self, | ||
endpoint: &str, | ||
params: &[(&str, &str)], | ||
) -> Result<T, ExecutionClientError> { | ||
let url = self.build_url(endpoint, params)?; | ||
self.http_client | ||
.get(url) | ||
.header(API_KEY_HEADER, &self.api_key) | ||
.send() | ||
.await? | ||
.json::<T>() | ||
.await | ||
.map_err(ExecutionClientError::http) | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
funds-manager/funds-manager-server/src/execution_client/quotes.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
//! Client methods for fetching quotes and prices from the execution venue | ||
|
||
use serde::Deserialize; | ||
|
||
use super::{error::ExecutionClientError, ExecutionClient}; | ||
|
||
/// The price endpoint | ||
const PRICE_ENDPOINT: &str = "swap/v1/price"; | ||
/// The quote endpoint | ||
const QUOTE_ENDPOINT: &str = "swap/v1/quote"; | ||
|
||
/// The buy token url param | ||
const BUY_TOKEN: &str = "buyToken"; | ||
/// The sell token url param | ||
const SELL_TOKEN: &str = "sellToken"; | ||
/// The sell amount url param | ||
const SELL_AMOUNT: &str = "sellAmount"; | ||
/// The taker address url param | ||
const TAKER_ADDRESS: &str = "takerAddress"; | ||
|
||
/// The price response | ||
#[derive(Debug, Deserialize)] | ||
pub struct ZeroXPriceResponse { | ||
/// The price | ||
pub price: String, | ||
} | ||
|
||
/// The subset of the quote response necessary for submitting a quote for | ||
/// execution | ||
#[derive(Debug, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct ExecutionQuote { | ||
/// The price of the quote | ||
pub price: String, | ||
/// The submitting address | ||
pub from: String, | ||
/// The to address | ||
pub to: String, | ||
/// The calldata for the swap | ||
pub data: String, | ||
/// The value of the tx; should be zero | ||
pub value: String, | ||
/// The gas price used in the swap | ||
pub gas_price: String, | ||
} | ||
|
||
impl ExecutionClient { | ||
/// Fetch a price for an asset | ||
pub async fn get_price( | ||
&self, | ||
buy_asset: &str, | ||
sell_asset: &str, | ||
amount: u128, | ||
) -> Result<f64, ExecutionClientError> { | ||
let amount_str = amount.to_string(); | ||
let params = | ||
[(BUY_TOKEN, buy_asset), (SELL_TOKEN, sell_asset), (SELL_AMOUNT, amount_str.as_str())]; | ||
|
||
let resp: ZeroXPriceResponse = self.get_execution(PRICE_ENDPOINT, ¶ms).await?; | ||
resp.price.parse::<f64>().map_err(ExecutionClientError::parse) | ||
} | ||
|
||
/// Fetch a quote for an asset | ||
pub async fn get_quote( | ||
&self, | ||
buy_asset: &str, | ||
sell_asset: &str, | ||
amount: u128, | ||
recipient: &str, | ||
) -> Result<ExecutionQuote, ExecutionClientError> { | ||
let amount_str = amount.to_string(); | ||
let params = [ | ||
(BUY_TOKEN, buy_asset), | ||
(SELL_TOKEN, sell_asset), | ||
(SELL_AMOUNT, amount_str.as_str()), | ||
(TAKER_ADDRESS, recipient), | ||
]; | ||
|
||
self.get_execution(QUOTE_ENDPOINT, ¶ms).await | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters