1use std::collections::HashSet;
2
3use alloy::primitives::{Address, U256, address, aliases::U24};
4use futures::future::join_all;
5
6use crate::{
7 Result,
8 asset::identity::AssetIdentifier,
9 network::NetworkId,
10 provider::RpcProvider,
11 quoter::{
12 AnyQuoter,
13 erc4626::{ERC4626, ERC4626Quoter},
14 uniswap_v2::{UniswapV2Quoter, factory::UniswapV2Factory, pair::UniswapV2Pair},
15 uniswap_v3::{UniswapV3Quoter, factory::UniswapV3Factory, pool::UniswapV3Pool},
16 },
17 router::Router,
18};
19
20const UNISWAP_V2_FACTORY: Address = address!("0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f");
21const UNISWAP_V3_FACTORY: Address = address!("0x1F98431c8aD98523631AE4a59f267346ea31F984");
22const DEFAULT_V3_FEES: &[u32] = &[100, 500, 3000, 10000];
23const MAX_CONFIDENCE: u64 = 100;
24
25#[derive(Debug, Clone)]
26#[allow(dead_code)]
27enum PoolKind {
28 V2,
29 V3(u32),
30}
31
32#[derive(Debug, Clone)]
33#[allow(dead_code)]
34struct DiscoveredPool {
35 pool_address: Address,
36 token0: Address,
37 token1: Address,
38 score: U256,
39 kind: PoolKind,
40}
41
42#[derive(Debug, Clone)]
43pub struct AutoRouter {
44 provider: RpcProvider,
45 tokens: Vec<AssetIdentifier>,
46 network_id: Option<NetworkId>,
47 uniswap_v2_factory: Option<Address>,
48 uniswap_v3_factory: Option<Address>,
49 uniswap_v3_fees: Vec<u32>,
50 min_liquidity: Option<U256>,
51 discover_v2: bool,
52 discover_v3: bool,
53 discover_erc4626: bool,
54}
55
56impl AutoRouter {
57 pub fn new(provider: RpcProvider, tokens: Vec<AssetIdentifier>) -> Self {
58 Self {
59 provider,
60 tokens,
61 network_id: None,
62 uniswap_v2_factory: None,
63 uniswap_v3_factory: None,
64 uniswap_v3_fees: DEFAULT_V3_FEES.to_vec(),
65 min_liquidity: Some(U256::from(1)),
66 discover_v2: true,
67 discover_v3: true,
68 discover_erc4626: true,
69 }
70 }
71
72 pub fn with_network_id(mut self, network_id: NetworkId) -> Self {
73 self.network_id = Some(network_id);
74 self
75 }
76
77 pub fn with_uniswap_v2_factory(mut self, address: Address) -> Self {
78 self.uniswap_v2_factory = Some(address);
79 self
80 }
81
82 pub fn with_uniswap_v3_factory(mut self, address: Address) -> Self {
83 self.uniswap_v3_factory = Some(address);
84 self
85 }
86
87 pub fn with_uniswap_v3_fees(mut self, fees: Vec<u32>) -> Self {
88 self.uniswap_v3_fees = fees;
89 self
90 }
91
92 pub fn with_min_liquidity(mut self, min: U256) -> Self {
93 self.min_liquidity = Some(min);
94 self
95 }
96
97 pub fn discover_uniswap_v2(mut self, enable: bool) -> Self {
98 self.discover_v2 = enable;
99 self
100 }
101
102 pub fn discover_uniswap_v3(mut self, enable: bool) -> Self {
103 self.discover_v3 = enable;
104 self
105 }
106
107 pub fn discover_erc4626(mut self, enable: bool) -> Self {
108 self.discover_erc4626 = enable;
109 self
110 }
111
112 pub async fn build(self) -> Result<Router> {
113 let network_id = match self.network_id {
114 Some(ref id) => id.clone(),
115 None => NetworkId::from_provider(&self.provider).await?,
116 };
117
118 let mut all_quoters: Vec<AnyQuoter> = Vec::new();
119
120 let mut extra_addresses: Vec<Address> = Vec::new();
122 if self.discover_erc4626 {
123 let (erc4626_quoters, underlying) = self.discover_erc4626_quoters(&network_id).await;
124 all_quoters.extend(erc4626_quoters);
125 extra_addresses = underlying;
126 }
127
128 let mut all_addresses: Vec<Address> = self.erc20_addresses();
130 let existing: HashSet<Address> = all_addresses.iter().copied().collect();
131 for addr in extra_addresses {
132 if !existing.contains(&addr) {
133 all_addresses.push(addr);
134 }
135 }
136
137 let mut v2_pools: Vec<DiscoveredPool> = Vec::new();
139 if self.discover_v2 {
140 v2_pools = Self::discover_v2_pools_inner(
141 &self.provider,
142 &all_addresses,
143 self.uniswap_v2_factory,
144 )
145 .await;
146 v2_pools = Self::filter_pools(Self::deduplicate_pools(v2_pools), &self.min_liquidity);
147 }
148
149 let mut v3_pools: Vec<DiscoveredPool> = Vec::new();
151 if self.discover_v3 {
152 v3_pools = Self::discover_v3_pools_inner(
153 &self.provider,
154 &all_addresses,
155 self.uniswap_v3_factory,
156 &self.uniswap_v3_fees,
157 )
158 .await;
159 v3_pools = Self::filter_pools(Self::deduplicate_pools(v3_pools), &self.min_liquidity);
160 }
161
162 for pool in v2_pools {
166 let quoter = UniswapV2Quoter {
167 network_id: network_id.clone(),
168 pair_address: pool.pool_address,
169 token0: pool.token0,
170 token1: pool.token1,
171 };
172 let confidence = pool_confidence_v2(pool.score);
173 all_quoters.push(AnyQuoter::from(quoter).with_confidence(confidence));
174 }
175
176 for pool in v3_pools {
177 let quoter = UniswapV3Quoter {
178 network_id: network_id.clone(),
179 pool_address: pool.pool_address,
180 token0: pool.token0,
181 token1: pool.token1,
182 };
183 let confidence = pool_confidence_v3(pool.score);
184 all_quoters.push(AnyQuoter::from(quoter).with_confidence(confidence));
185 }
186
187 if all_quoters.is_empty() {
188 return Err(crate::error::EthPricesError::AutoRouterNoPools);
189 }
190
191 Ok(Router::from_iter(all_quoters))
192 }
193
194 fn erc20_addresses(&self) -> Vec<Address> {
195 self.tokens
196 .iter()
197 .filter_map(|t| match t {
198 AssetIdentifier::ERC20 { address } => Some(*address),
199 _ => None,
200 })
201 .collect()
202 }
203
204 fn sorted_pair(a: Address, b: Address) -> (Address, Address) {
205 if a < b { (a, b) } else { (b, a) }
206 }
207
208 fn deduplicate_pools(pools: Vec<DiscoveredPool>) -> Vec<DiscoveredPool> {
209 let mut best: std::collections::HashMap<(Address, Address), DiscoveredPool> =
210 std::collections::HashMap::new();
211
212 for pool in pools {
213 let key = Self::sorted_pair(pool.token0, pool.token1);
214 match best.get(&key) {
215 Some(existing) if existing.score >= pool.score => continue,
216 _ => {
217 best.insert(key, pool);
218 }
219 }
220 }
221
222 best.into_values().collect()
223 }
224
225 fn filter_pools(
226 pools: Vec<DiscoveredPool>,
227 min_liquidity: &Option<U256>,
228 ) -> Vec<DiscoveredPool> {
229 match min_liquidity {
230 Some(min) => pools.into_iter().filter(|p| p.score >= *min).collect(),
231 None => pools,
232 }
233 }
234
235 async fn discover_v2_pools_inner(
236 provider: &RpcProvider,
237 addresses: &[Address],
238 factory_opt: Option<Address>,
239 ) -> Vec<DiscoveredPool> {
240 let factory = factory_opt.unwrap_or(UNISWAP_V2_FACTORY);
241
242 let mut pairs = Vec::new();
243 for i in 0..addresses.len() {
244 for j in (i + 1)..addresses.len() {
245 pairs.push((addresses[i], addresses[j]));
246 }
247 }
248
249 let results: Vec<_> = join_all(pairs.into_iter().map(|(a, b)| {
250 let provider = provider.clone();
251 async move { discover_single_v2_pool(&provider, factory, a, b).await }
252 }))
253 .await;
254
255 let pools: Vec<DiscoveredPool> = results.into_iter().flatten().collect();
256
257 let liq_futures: Vec<_> = pools
258 .iter()
259 .map(|pool| {
260 let provider = provider.clone();
261 async move {
262 let pair = UniswapV2Pair::new(pool.pool_address, &provider);
263 match pair.getReserves().call().await {
264 Ok(reserves) => {
265 let reserve0 = U256::from(reserves.reserve0);
266 let reserve1 = U256::from(reserves.reserve1);
267 Some(std::cmp::min(reserve0, reserve1))
268 }
269 Err(_) => None,
270 }
271 }
272 })
273 .collect();
274
275 let scores: Vec<Option<U256>> = join_all(liq_futures).await;
276
277 pools
278 .into_iter()
279 .zip(scores)
280 .filter_map(|(mut pool, score)| {
281 pool.score = score?;
282 Some(pool)
283 })
284 .collect()
285 }
286
287 async fn discover_v3_pools_inner(
288 provider: &RpcProvider,
289 addresses: &[Address],
290 factory_opt: Option<Address>,
291 fees: &[u32],
292 ) -> Vec<DiscoveredPool> {
293 let factory = factory_opt.unwrap_or(UNISWAP_V3_FACTORY);
294
295 if addresses.len() < 2 {
296 return Vec::new();
297 }
298
299 let mut queries = Vec::new();
300 for i in 0..addresses.len() {
301 for j in (i + 1)..addresses.len() {
302 let a = addresses[i];
303 let b = addresses[j];
304 for &fee in fees {
305 queries.push((a, b, fee));
306 }
307 }
308 }
309
310 join_all(queries.into_iter().map(|(a, b, fee)| {
311 let provider = provider.clone();
312 async move { discover_single_v3_pool(&provider, factory, a, b, fee).await }
313 }))
314 .await
315 .into_iter()
316 .flatten()
317 .collect()
318 }
319
320 async fn discover_erc4626_quoters(
321 &self,
322 network_id: &NetworkId,
323 ) -> (Vec<AnyQuoter>, Vec<Address>) {
324 let addresses = self.erc20_addresses();
325
326 let results: Vec<_> = join_all(addresses.into_iter().map(|addr| {
327 let provider = self.provider.clone();
328 let net_id = network_id.clone();
329 async move {
330 match ERC4626::new(addr, &provider).asset().call().await {
331 Ok(underlying) => {
332 let quoter = ERC4626Quoter {
333 network_id: net_id,
334 vault_address: AssetIdentifier::ERC20 { address: addr },
335 token_address: AssetIdentifier::ERC20 {
336 address: underlying,
337 },
338 };
339 Some((AnyQuoter::from(quoter).with_confidence(50), underlying))
340 }
341 Err(_) => None,
342 }
343 }
344 }))
345 .await;
346
347 let (quoters, underlying): (Vec<_>, Vec<_>) = results.into_iter().flatten().unzip();
348
349 (quoters, underlying)
350 }
351}
352
353fn pool_confidence_v2(score: U256) -> u64 {
354 if score.is_zero() {
355 return 0;
356 }
357 let divisor = U256::from(1_000_000_000u64);
358 let scaled = score / divisor;
359 if scaled >= U256::from(MAX_CONFIDENCE) {
360 MAX_CONFIDENCE
361 } else {
362 scaled.as_limbs()[0]
363 }
364}
365
366fn pool_confidence_v3(score: U256) -> u64 {
367 if score.is_zero() {
368 return 0;
369 }
370 let divisor = U256::from(10_000_000_000_000_000u64);
371 let scaled = score / divisor;
372 if scaled >= U256::from(MAX_CONFIDENCE) {
373 MAX_CONFIDENCE
374 } else {
375 scaled.as_limbs()[0]
376 }
377}
378
379async fn discover_single_v2_pool(
380 provider: &RpcProvider,
381 factory: Address,
382 token_a: Address,
383 token_b: Address,
384) -> Option<DiscoveredPool> {
385 let v2_factory = UniswapV2Factory::new(factory, provider);
386 let pair = v2_factory.getPair(token_a, token_b).call().await.ok()?;
387 if pair.is_zero() {
388 return None;
389 }
390
391 let pair_contract = UniswapV2Pair::new(pair, provider);
392 let token0 = pair_contract.token0().call().await.ok()?;
393 let token1 = pair_contract.token1().call().await.ok()?;
394
395 Some(DiscoveredPool {
396 pool_address: pair,
397 token0,
398 token1,
399 score: U256::ZERO,
400 kind: PoolKind::V2,
401 })
402}
403
404async fn discover_single_v3_pool(
405 provider: &RpcProvider,
406 factory: Address,
407 token_a: Address,
408 token_b: Address,
409 fee: u32,
410) -> Option<DiscoveredPool> {
411 let v3_factory = UniswapV3Factory::new(factory, provider);
412 let pool = v3_factory
413 .getPool(token_a, token_b, U24::from(fee))
414 .call()
415 .await
416 .ok()?;
417 if pool.is_zero() {
418 return None;
419 }
420
421 let pool_contract = UniswapV3Pool::new(pool, provider);
422 let token0 = pool_contract.token0().call().await.ok()?;
423 let token1 = pool_contract.token1().call().await.ok()?;
424 let liq: u128 = pool_contract.liquidity().call().await.ok()?;
425
426 Some(DiscoveredPool {
427 pool_address: pool,
428 token0,
429 token1,
430 score: U256::from(liq),
431 kind: PoolKind::V3(fee),
432 })
433}