1use alloy::primitives::{U256, map::HashMap};
2use figment::{
3 Figment,
4 providers::{Format, Toml},
5};
6use serde::Deserialize;
7
8use crate::{
9 Result,
10 error::EthPricesError,
11 provider::RpcProvider,
12 quoter::{
13 AnyQuoter,
14 chainlink::{ChainlinkConfig, ChainlinkQuoter},
15 erc4626::{ERC4626Config, ERC4626Quoter},
16 fixed::FixedQuoter,
17 uniswap_v2::{UniswapV2Config, UniswapV2Quoter},
18 uniswap_v3::{UniswapV3Quoter, discovery::UniswapV3Config},
19 },
20};
21
22#[derive(Debug, Deserialize, PartialEq)]
23pub struct Config {
24 pub chains: HashMap<String, ChainConfig>,
25}
26
27#[derive(Debug, Deserialize, PartialEq)]
28pub struct ChainConfig {
29 pub chain_id: u64,
30 pub rpc_url: String,
31 pub tokens: Vec<TokenConfig>,
32 pub quoters: QuotersConfig,
33}
34
35#[derive(Debug, Deserialize, PartialEq, Clone)]
36pub struct QuotersConfig {
37 pub fixed: Vec<FixedQuoter>,
38 pub uniswap_v2: Option<UniswapV2Config>,
39 pub uniswap_v3: Option<UniswapV3Config>,
40 pub erc4626: Vec<ERC4626Config>,
41 #[serde(default)]
42 pub chainlink: Vec<ChainlinkConfig>,
43}
44
45impl QuotersConfig {
46 pub async fn all(self, provider: &RpcProvider) -> Result<Vec<AnyQuoter>> {
47 let mut quoters: Vec<AnyQuoter> = Vec::new();
48 for tracker in self.fixed {
49 if tracker.fixed_rate <= U256::from(0) {
50 return Err(EthPricesError::InvalidConfiguration(format!(
51 "Fixed rate for {} to {} must be > 0.0",
52 tracker.token_in, tracker.token_out
53 )));
54 }
55 quoters.push(tracker.into());
56 }
57
58 if let Some(uniswap_v2_config) = &self.uniswap_v2 {
59 for uni_quoters in uniswap_v2_config.pairs.iter() {
60 let quoter = UniswapV2Quoter::from_selector(provider, uni_quoters.clone()).await?;
61 quoters.push(quoter.into());
62 }
63 }
64
65 if let Some(uniswap_v3_config) = &self.uniswap_v3 {
66 for uni_quoters in uniswap_v3_config.pools.iter() {
67 let quoter = UniswapV3Quoter::from_selector(provider, uni_quoters.clone()).await?;
68 quoters.push(quoter.into());
69 }
70 }
71
72 for erc4626_config in &self.erc4626 {
73 let quoter = ERC4626Quoter::new(erc4626_config.vault_address, provider).await?;
74 quoters.push(quoter.into());
75 }
76
77 for chainlink_config in &self.chainlink {
78 let quoter = ChainlinkQuoter::new(
79 chainlink_config.contract,
80 chainlink_config.token.clone(),
81 chainlink_config.token_decimals,
82 chainlink_config.quote.clone(),
83 chainlink_config.quote_decimals,
84 provider,
85 )
86 .await?;
87 quoters.push(quoter.into());
88 }
89
90 Ok(quoters)
91 }
92}
93
94#[derive(Debug, Deserialize, PartialEq)]
95pub struct TokenConfig {
96 pub address: String,
97 pub slug: Option<String>,
98 pub decimals: u8,
99}
100
101impl Config {
102 pub async fn load(path: &str) -> Result<Self> {
103 let figment = Figment::new().merge(Toml::file(path));
104 figment
105 .extract::<Config>()
106 .map_err(|e| EthPricesError::ConfigError(e.to_string()))
107 }
108}