Skip to content

Commit 85da40c

Browse files
authored
Merge pull request #397 from propeller-heads/rfqs/tnl/hashflow-builder
feat: HashflowClientBuilder
2 parents 7276c87 + dd8d128 commit 85da40c

File tree

6 files changed

+121
-81
lines changed

6 files changed

+121
-81
lines changed

examples/rfq_quickstart/main.rs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ use tycho_simulation::{
3636
rfq::{
3737
protocols::{
3838
bebop::{client_builder::BebopClientBuilder, state::BebopState},
39-
hashflow::{client::HashflowClient, state::HashflowState},
39+
hashflow::{client_builder::HashflowClientBuilder, state::HashflowState},
4040
},
4141
stream::RFQStreamBuilder,
4242
},
@@ -179,16 +179,12 @@ async fn main() {
179179
}
180180
if let (Some(user), Some(key)) = (hashflow_user, hashflow_key) {
181181
println!("Setting up Hashflow RFQ client...\n");
182-
let hashflow_client = HashflowClient::new(
183-
chain,
184-
rfq_tokens,
185-
cli.tvl_threshold,
186-
Default::default(),
187-
user,
188-
key,
189-
Duration::from_secs(5),
190-
)
191-
.expect("Failed to create Hashflow RFQ client");
182+
let hashflow_client = HashflowClientBuilder::new(chain, user, key)
183+
.tokens(rfq_tokens)
184+
.tvl_threshold(cli.tvl_threshold)
185+
.poll_time(Duration::from_secs(5))
186+
.build()
187+
.expect("Failed to create Hashflow RFQ client");
192188
rfq_stream_builder =
193189
rfq_stream_builder.add_client::<HashflowState>("hashflow", Box::new(hashflow_client))
194190
}

src/rfq/protocols/bebop/client_builder.rs

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -79,48 +79,3 @@ impl BebopClientBuilder {
7979
BebopClient::new(self.chain, self.tokens, self.tvl, self.ws_user, self.ws_key, quote_tokens)
8080
}
8181
}
82-
83-
#[cfg(test)]
84-
mod tests {
85-
use std::str::FromStr;
86-
87-
use super::*;
88-
89-
#[test]
90-
fn test_bebop_client_builder_basic_config() {
91-
let mut tokens = HashSet::new();
92-
tokens.insert(Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap());
93-
tokens.insert(Bytes::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap());
94-
95-
let result = BebopClientBuilder::new(
96-
Chain::Ethereum,
97-
"test_user".to_string(),
98-
"test_key".to_string(),
99-
)
100-
.tokens(tokens)
101-
.build();
102-
assert!(result.is_ok());
103-
}
104-
105-
#[test]
106-
fn test_bebop_client_builder_custom_configuration() {
107-
let mut custom_quote_tokens = HashSet::new();
108-
custom_quote_tokens
109-
.insert(Bytes::from_str("0x1234567890123456789012345678901234567890").unwrap());
110-
111-
let mut tokens = HashSet::new();
112-
tokens.insert(Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap());
113-
tokens.insert(Bytes::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap());
114-
115-
let result = BebopClientBuilder::new(
116-
Chain::Ethereum,
117-
"test_user".to_string(),
118-
"test_key".to_string(),
119-
)
120-
.tokens(tokens)
121-
.tvl_threshold(500.0)
122-
.quote_tokens(custom_quote_tokens.clone())
123-
.build();
124-
assert!(result.is_ok());
125-
}
126-
}

src/rfq/protocols/hashflow/client.rs

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,9 @@ use crate::{
2323
client::RFQClient,
2424
errors::RFQError,
2525
models::TimestampHeader,
26-
protocols::{
27-
hashflow::models::{
28-
HashflowChain, HashflowMarketMakerLevels, HashflowMarketMakersResponse,
29-
HashflowPriceLevelsResponse, HashflowQuoteRequest, HashflowQuoteResponse,
30-
HashflowRFQ,
31-
},
32-
utils::default_quote_tokens_for_chain,
26+
protocols::hashflow::models::{
27+
HashflowChain, HashflowMarketMakerLevels, HashflowMarketMakersResponse,
28+
HashflowPriceLevelsResponse, HashflowQuoteRequest, HashflowQuoteResponse, HashflowRFQ,
3329
},
3430
},
3531
tycho_client::feed::synchronizer::{ComponentWithState, Snapshot, StateSyncMessage},
@@ -66,12 +62,6 @@ impl HashflowClient {
6662
auth_key: String,
6763
poll_time: Duration,
6864
) -> Result<Self, RFQError> {
69-
let quote_tokens = if quote_tokens.is_empty() {
70-
default_quote_tokens_for_chain(&chain)?
71-
} else {
72-
quote_tokens
73-
};
74-
7565
Ok(Self {
7666
chain,
7767
price_levels_endpoint: "https://api.hashflow.com/taker/v3/price-levels".to_string(),
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use std::collections::HashSet;
2+
3+
use tokio::time::Duration;
4+
use tycho_common::{models::Chain, Bytes};
5+
6+
use super::client::HashflowClient;
7+
use crate::rfq::{errors::RFQError, protocols::utils::default_quote_tokens_for_chain};
8+
9+
/// `HashflowClientBuilder` is a builder pattern implementation for creating instances of
10+
/// `HashflowClient`.
11+
///
12+
/// # Example
13+
/// ```rust
14+
/// use tycho_simulation::rfq::protocols::hashflow::client_builder::HashflowClientBuilder;
15+
/// use tycho_common::{models::Chain, Bytes};
16+
/// use std::{collections::HashSet, str::FromStr, time::Duration};
17+
///
18+
/// let mut tokens = HashSet::new();
19+
/// tokens.insert(Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap()); // WETH
20+
/// tokens.insert(Bytes::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap()); // USDC
21+
///
22+
/// let client = HashflowClientBuilder::new(
23+
/// Chain::Ethereum,
24+
/// "auth_user".to_string(),
25+
/// "auth_key".to_string()
26+
/// )
27+
/// .tokens(tokens)
28+
/// .tvl_threshold(500.0)
29+
/// .poll_time(Duration::from_secs(10))
30+
/// .build()
31+
/// .unwrap();
32+
/// ```
33+
pub struct HashflowClientBuilder {
34+
chain: Chain,
35+
auth_user: String,
36+
auth_key: String,
37+
tokens: HashSet<Bytes>,
38+
tvl: f64,
39+
quote_tokens: Option<HashSet<Bytes>>,
40+
poll_time: Duration,
41+
}
42+
43+
impl HashflowClientBuilder {
44+
pub fn new(chain: Chain, auth_user: String, auth_key: String) -> Self {
45+
Self {
46+
chain,
47+
auth_user,
48+
auth_key,
49+
tokens: HashSet::new(),
50+
tvl: 100.0, // Default $100 minimum TVL
51+
quote_tokens: None,
52+
poll_time: Duration::from_secs(5), // Default 5 second polling
53+
}
54+
}
55+
56+
/// Set the tokens for which to monitor prices
57+
pub fn tokens(mut self, tokens: HashSet<Bytes>) -> Self {
58+
self.tokens = tokens;
59+
self
60+
}
61+
62+
/// Set the minimum TVL threshold for pools
63+
pub fn tvl_threshold(mut self, tvl: f64) -> Self {
64+
self.tvl = tvl;
65+
self
66+
}
67+
68+
/// Set custom quote tokens for TVL calculation
69+
/// If not set, will use chain-specific defaults
70+
pub fn quote_tokens(mut self, quote_tokens: HashSet<Bytes>) -> Self {
71+
self.quote_tokens = Some(quote_tokens);
72+
self
73+
}
74+
75+
/// Set the polling interval for fetching price levels
76+
pub fn poll_time(mut self, poll_time: Duration) -> Self {
77+
self.poll_time = poll_time;
78+
self
79+
}
80+
81+
pub fn build(self) -> Result<HashflowClient, RFQError> {
82+
let quote_tokens;
83+
if let Some(tokens) = self.quote_tokens {
84+
quote_tokens = tokens;
85+
} else {
86+
quote_tokens = default_quote_tokens_for_chain(&self.chain)?
87+
}
88+
89+
HashflowClient::new(
90+
self.chain,
91+
self.tokens,
92+
self.tvl,
93+
quote_tokens,
94+
self.auth_user,
95+
self.auth_key,
96+
self.poll_time,
97+
)
98+
}
99+
}

src/rfq/protocols/hashflow/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod client;
2+
pub mod client_builder;
23
mod models;
34
pub mod state;
45
pub mod tycho_decoder;

tycho-integration-test/src/stream_processor/rfq_stream_processor.rs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ use tycho_common::{
1616
use tycho_simulation::rfq::{
1717
protocols::{
1818
bebop::{client::BebopClient, client_builder::BebopClientBuilder, state::BebopState},
19-
hashflow::{client::HashflowClient, state::HashflowState},
19+
hashflow::{
20+
client::HashflowClient, client_builder::HashflowClientBuilder, state::HashflowState,
21+
},
2022
},
2123
stream::RFQStreamBuilder,
2224
};
@@ -102,17 +104,14 @@ impl RFQStreamProcessor {
102104
.add_client::<BebopState>("bebop", Box::new(bebop_client));
103105
}
104106
RFQProtocol::Hashflow => {
105-
let hashflow_client = HashflowClient::new(
106-
self.chain,
107-
rfq_tokens.clone(),
108-
self.tvl_threshold,
109-
Default::default(),
110-
user.clone(),
111-
key.clone(),
112-
Duration::from_secs(30),
113-
)
114-
.into_diagnostic()
115-
.wrap_err("Failed to create Hashflow RFQ client")?;
107+
let hashflow_client =
108+
HashflowClientBuilder::new(self.chain, user.clone(), key.clone())
109+
.tokens(rfq_tokens.clone())
110+
.tvl_threshold(self.tvl_threshold)
111+
.poll_time(Duration::from_secs(30))
112+
.build()
113+
.into_diagnostic()
114+
.wrap_err("Failed to create Hashflow RFQ client")?;
116115
rfq_stream_builder = rfq_stream_builder
117116
.add_client::<HashflowState>("hashflow", Box::new(hashflow_client))
118117
}

0 commit comments

Comments
 (0)