kabu_defi_price/
price_actor.rs1use eyre::eyre;
2use kabu_core_components::Component;
3use std::collections::HashMap;
4use std::marker::PhantomData;
5use std::ops::{Div, Mul};
6use std::sync::Arc;
7use std::time::Duration;
8use tokio::sync::RwLock;
9
10use alloy_network::Network;
11use alloy_primitives::{Address, U256, address};
12use alloy_provider::Provider;
13
14use kabu_core_blockchain::Blockchain;
15use kabu_defi_address_book::TokenAddressEth;
16use kabu_defi_pools::CurvePool;
17use kabu_defi_pools::protocols::CurveProtocol;
18use kabu_types_market::{Market, Pool};
19use tracing::{debug, error, info};
20
21async fn price_worker<N: Network, P: Provider<N> + Clone + 'static>(
22 client: P,
23 market: Arc<RwLock<Market>>,
24 once: bool,
25) -> eyre::Result<()> {
26 let curve_tricrypto_usdc = CurveProtocol::new_u256_3_eth_to(client.clone(), address!("7F86Bf177Dd4F3494b841a37e810A34dD56c829B"));
27 let curve_tricrypto_usdt = CurveProtocol::new_u256_3_eth_to(client.clone(), address!("f5f5b97624542d72a9e06f04804bf81baa15e2b4"));
28
29 let mut coins_hash_map: HashMap<Address, CurvePool<P, N>> = HashMap::new();
30
31 let curve_tricrypto_usdc_pool = CurvePool::fetch_pool_data(client.clone(), curve_tricrypto_usdc).await?;
32
33 let curve_tricrypto_usdt_pool = CurvePool::fetch_pool_data(client.clone(), curve_tricrypto_usdt).await?;
34
35 coins_hash_map.insert(TokenAddressEth::USDC, curve_tricrypto_usdc_pool.clone());
36 coins_hash_map.insert(TokenAddressEth::WBTC, curve_tricrypto_usdc_pool.clone());
37 coins_hash_map.insert(TokenAddressEth::USDT, curve_tricrypto_usdt_pool.clone());
38
39 let one_ether = U256::from(10).pow(U256::from(18));
40 let weth_amount = one_ether.mul(U256::from(5));
41
42 match market.read().await.get_token(&TokenAddressEth::WETH) {
43 Some(token) => {
44 token.set_eth_price(Some(one_ether));
45 }
46 _ => {
47 error!("WETH_NOT_FOUND")
48 }
49 }
50
51 loop {
52 for (token_address, curve_pool) in coins_hash_map.iter() {
53 debug!("Fetching price of {} at {}", token_address, curve_pool.get_address());
54
55 match curve_pool.fetch_out_amount(TokenAddressEth::WETH, *token_address, weth_amount).await {
56 Ok(out_amount) => {
57 let price = out_amount.mul(one_ether).div(weth_amount);
58 info!("Price of ETH in {token_address:#20x} is {price}");
59 match market.read().await.get_token(token_address) {
60 Some(tkn) => {
61 tkn.set_eth_price(Some(price));
62 debug!("Price is set");
63 }
64 _ => {
65 error!(address=%token_address, "Token not found");
66 }
67 }
68 }
69 Err(error) => {
70 error!(%error, "fetch_out_amount")
71 }
72 }
73 }
74
75 let usdt_price = market.read().await.get_token_or_default(&TokenAddressEth::USDT).get_eth_price();
76 let usdc_price = market.read().await.get_token_or_default(&TokenAddressEth::USDC).get_eth_price();
77
78 let mut usd_price: Option<U256> = None;
79 if let Some(usdc_price) = usdc_price
80 && let Some(usdt_price) = usdt_price
81 {
82 usd_price = Some((usdc_price + usdt_price) >> 1);
83 }
84
85 if let Some(usd_price) = usd_price {
86 match market.read().await.get_token(&TokenAddressEth::DAI) {
87 Some(tkn) => {
88 tkn.set_eth_price(Some(U256::from(10).pow(U256::from(12)).mul(usd_price)));
89 }
90 _ => {
91 error!("Token {:#20x} not found", TokenAddressEth::DAI);
92 }
93 }
94 }
95 if once {
96 break;
97 }
98
99 let _ = tokio::time::sleep(Duration::new(60, 0)).await;
100 }
101 Ok(())
102}
103
104#[derive(Clone)]
105pub struct PriceComponent<P, N> {
106 client: P,
107 only_once: bool,
108
109 market: Option<Arc<RwLock<Market>>>,
110 _n: PhantomData<N>,
111}
112
113impl<P, N> PriceComponent<P, N>
114where
115 N: Network,
116 P: Provider<N> + Send + Sync + Clone + 'static,
117{
118 pub fn new(client: P) -> Self {
119 Self { client, only_once: false, market: None, _n: PhantomData }
120 }
121
122 pub fn only_once(self) -> Self {
123 Self { only_once: true, ..self }
124 }
125
126 pub fn on_bc(self, bc: &Blockchain) -> Self {
127 Self { market: Some(bc.market()), ..self }
128 }
129
130 pub fn with_market(self, market: Arc<RwLock<Market>>) -> Self {
131 Self { market: Some(market), ..self }
132 }
133}
134
135impl<P, N> Component for PriceComponent<P, N>
136where
137 N: Network,
138 P: Provider<N> + Send + Sync + Clone + 'static,
139{
140 fn spawn(self, executor: reth_tasks::TaskExecutor) -> eyre::Result<()> {
141 let name = self.name();
142 let market = self.market.ok_or_else(|| eyre!("Market is not set"))?;
143
144 executor.spawn_critical(name, async move {
145 if let Err(e) = price_worker(self.client, market, self.only_once).await {
146 tracing::error!("Price worker failed: {}", e);
147 }
148 });
149 Ok(())
150 }
151
152 fn name(&self) -> &'static str {
153 "PriceComponent"
154 }
155}