Skip to main content

eth_prices/config/
mod.rs

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}