tycho_simulation/
utils.rs1use std::collections::HashMap;
2
3use tracing::info;
4use tycho_client::{
5 rpc::{HttpRPCClientOptions, RPCClient, RPC_CLIENT_CONCURRENCY},
6 HttpRPCClient, RPCError,
7};
8use tycho_common::{
9 models::{token::Token, Chain},
10 simulation::errors::SimulationError,
11 Bytes,
12};
13
14pub fn hexstring_to_vec(hexstring: &str) -> Result<Vec<u8>, SimulationError> {
37 let hexstring_no_prefix =
38 if let Some(stripped) = hexstring.strip_prefix("0x") { stripped } else { hexstring };
39 let bytes = hex::decode(hexstring_no_prefix).map_err(|err| {
40 SimulationError::FatalError(format!("Invalid hex string `{hexstring}`: {err}"))
41 })?;
42 Ok(bytes)
43}
44
45pub async fn load_all_tokens(
62 tycho_url: &str,
63 no_tls: bool,
64 auth_key: Option<&str>,
65 compression: bool,
66 chain: Chain,
67 min_quality: Option<i32>,
68 max_days_since_last_trade: Option<u64>,
69) -> Result<HashMap<Bytes, Token>, SimulationError> {
70 info!("Loading tokens from Tycho...");
71 let rpc_url =
72 if no_tls { format!("http://{tycho_url}") } else { format!("https://{tycho_url}") };
73
74 let rpc_options = HttpRPCClientOptions::new()
75 .with_auth_key(auth_key.map(|s| s.to_string()))
76 .with_compression(compression);
77
78 let rpc_client = HttpRPCClient::new(rpc_url.as_str(), rpc_options)
79 .map_err(|err| map_rpc_error(err, "Failed to create Tycho RPC client"))?;
80
81 let default_min_days = HashMap::from([(Chain::Base, 1_u64), (Chain::Unichain, 14_u64)]);
83
84 #[allow(clippy::mutable_key_type)]
85 let tokens = rpc_client
86 .get_all_tokens(
87 chain.into(),
88 min_quality.or(Some(100)),
89 max_days_since_last_trade.or(default_min_days
90 .get(&chain)
91 .or(Some(&42))
92 .copied()),
93 Some(3000),
94 RPC_CLIENT_CONCURRENCY,
95 )
96 .await
97 .map_err(|err| map_rpc_error(err, "Unable to load tokens"))?;
98
99 tokens
100 .into_iter()
101 .map(|token| {
102 let token_clone = token.clone();
103 Token::try_from(token)
104 .map(|converted| (converted.address.clone(), converted))
105 .map_err(|_| {
106 SimulationError::FatalError(format!(
107 "Unable to convert token `{symbol}` at {address} on chain {chain} into ERC20 token",
108 symbol = token_clone.symbol,
109 address = token_clone.address,
110 chain = token_clone.chain,
111 ))
112 })
113 })
114 .collect()
115}
116
117pub fn get_default_url(chain: &Chain) -> Option<String> {
119 match chain {
120 Chain::Ethereum => Some("tycho-beta.propellerheads.xyz".to_string()),
121 Chain::Base => Some("tycho-base-beta.propellerheads.xyz".to_string()),
122 Chain::Unichain => Some("tycho-unichain-beta.propellerheads.xyz".to_string()),
123 _ => None,
124 }
125}
126
127fn map_rpc_error(err: RPCError, context: &str) -> SimulationError {
128 let message = format!("{context}: {err}", err = err,);
129 match err {
130 RPCError::UrlParsing(_, _) | RPCError::FormatRequest(_) => {
131 SimulationError::InvalidInput(message, None)
132 }
133 _ => SimulationError::FatalError(message),
134 }
135}