Skip to content

Commit

Permalink
feat(indexer): retrieve L1 deposit transaction price
Browse files Browse the repository at this point in the history
Ethereum price is provided by Moralis with WETH uniswap v3
  • Loading branch information
ptisserand committed Apr 17, 2024
1 parent 85cc68b commit 36dd031
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 6 deletions.
7 changes: 3 additions & 4 deletions apps/indexer/src/ethereum_indexer/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,24 +208,23 @@ impl EthereumClient {
let status = messaging.l2_to_l1_messages(msg_hash).call().await?;
match status.try_into() {
Ok(s) => Ok(s),
Err(e) => Err(anyhow!("Failed to retrieve message status: {:?}", e))
Err(e) => Err(anyhow!("Failed to retrieve message status: {:?}", e)),
}
}

/// Retrieve gas used for a given transaction
pub async fn get_tx_fees(&self, transaction_hash: &str) -> Result<u128> {
pub async fn get_tx_fees(&self, transaction_hash: &str) -> Result<u64> {
let tx_hash: TxHash = H256::from_str(transaction_hash).unwrap();
if let Some(receipt) = self.provider.get_transaction_receipt(tx_hash).await? {
let effective_gas_price = receipt.effective_gas_price.unwrap();
let gas_used = receipt.gas_used.unwrap();
let total_fees = effective_gas_price * gas_used;
match total_fees.try_into() {
Ok(fees) => Ok(fees),
Err(e) => Err(anyhow!("{:?}", e))
Err(e) => Err(anyhow!("{:?}", e)),
}
} else {
Err(anyhow!("Failed to get receipt for {}", transaction_hash))
}
}

}
1 change: 1 addition & 0 deletions apps/indexer/src/ethereum_indexer/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub fn get_store_data(log: Log) -> Result<(Option<Request>, Option<Event>, Optio
block_timestamp: 0,
block_number: log.block_number.unwrap().try_into().unwrap(),
tx_hash: format!("{:#x}", log.transaction_hash.unwrap()),
price: None,
};

// TODO: not a fan of the mut here and for event, but as the type of data can change,
Expand Down
34 changes: 32 additions & 2 deletions apps/indexer/src/ethereum_indexer/indexer.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use super::client::EthereumClient;
use super::events;
use crate::config::{ChainConfig, XchainTxConfig};
use crate::price::moralis::MoralisPrice;
use crate::storage::{
store::{BlockStore, CrossChainTxStore, EventStore, PendingWithdrawStore, RequestStore},
BlockIndex, BridgeChain, CrossChainTxKind, Event, EventLabel,
BlockIndex, BridgeChain, CrossChainTxKind, Event, EventLabel, EventPrice,
};
use crate::utils;
use crate::ChainsBlocks;
Expand All @@ -23,6 +24,7 @@ pub struct EthereumIndexer<
store: Arc<T>,
chains_blocks: Arc<AsyncRwLock<ChainsBlocks>>,
xchain_txor_config: XchainTxConfig,
pricer: MoralisPrice,
}

impl<T> EthereumIndexer<T>
Expand All @@ -37,12 +39,15 @@ where
xchain_txor_config: XchainTxConfig,
) -> Result<EthereumIndexer<T>> {
let client = EthereumClient::new(config.clone()).await?;
/// TODO: should we add moralis api key to configuration file?
let pricer = MoralisPrice::new(None);
Ok(EthereumIndexer {
client,
config,
store,
chains_blocks,
xchain_txor_config,
pricer,
})
}

Expand Down Expand Up @@ -238,8 +243,18 @@ where
let l_sig = l.topics[0];

match events::get_store_data(l)? {
(Some(r), Some(e), xchain_tx) => {
(Some(r), Some(mut e), xchain_tx) => {
log::debug!("Request/Event/Tx\n{:?}\n{:?}\n{:?}", r, e, xchain_tx);
if e.label == EventLabel::DepositInitiatedL1 {
match self.compute_event_price(&e).await {
Ok(price) => {
log::debug!("Price: {:?}", price);
e.price = Some(price);
}
Err(e) => log::warn!("Failed to compute event price: {:?}", e),
}
}

self.store.insert_event(e.clone()).await?;

if self.store.req_by_hash(&r.hash).await?.is_none() {
Expand Down Expand Up @@ -313,4 +328,19 @@ where
}
Ok(())
}

async fn compute_event_price(&self, e: &Event) -> Result<EventPrice> {
let gas = self.client.get_tx_fees(&e.tx_hash).await?;
let eth_price = self
.pricer
.get_price("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", None)
.await?;
let mut usd_price = (gas as f64) * eth_price.parse::<f64>()?;
usd_price = usd_price / (10_u64.pow(18) as f64);

Ok(EventPrice {
gas,
usd_price: format!("{}", usd_price),
})
}
}
1 change: 1 addition & 0 deletions apps/indexer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use tokio::sync::RwLock as AsyncRwLock;
pub mod config;
pub mod ethereum_indexer;
pub mod handlers;
pub mod price;
pub mod starknet_indexer;
pub mod storage;
pub mod utils;
Expand Down
1 change: 1 addition & 0 deletions apps/indexer/src/price/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod moralis;
61 changes: 61 additions & 0 deletions apps/indexer/src/price/moralis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use std::env;

use reqwest::{
self,
header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE},
};

use serde::Deserialize;

use anyhow::{anyhow, Result};

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct APIResponse {
usd_price_formatted: String,
}

pub struct MoralisPrice {
client: reqwest::Client,
headers: HeaderMap,
}

impl MoralisPrice {
pub fn new(api_key: Option<&str>) -> MoralisPrice {
let api_key = if api_key.is_none() {
env::var("MORALIS_API_KEY").expect("MORALIS_API_KEY environment variable")
} else {
api_key.unwrap().to_owned()
};
let client = reqwest::Client::new();
let mut headers = HeaderMap::new();
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
headers.insert("X-API-KEY", HeaderValue::from_str(&api_key).unwrap());
MoralisPrice { client, headers }
}

pub async fn get_price(&self, token: &str, block: Option<u64>) -> Result<String> {
let base_url = "https://deep-index.moralis.io/api/v2.2/erc20";
let url = if block.is_some() {
let block = block.unwrap();
format!("{base_url}/{token}/price?chain=eth&to_block={block}")
} else {
format!("{base_url}/{token}/price?chain=eth")
};
let response = self
.client
.get(url)
.headers(self.headers.clone())
.send()
.await?;
if response.status().is_success() {
match response.json::<APIResponse>().await {
Ok(parsed) => Ok(parsed.usd_price_formatted),
Err(_) => Err(anyhow!("Failed to parse response")),
}
} else {
Err(anyhow!("{:?}", response.error_for_status()))
}
}
}
1 change: 1 addition & 0 deletions apps/indexer/src/starknet_indexer/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub fn get_store_data(
block_timestamp: block_timestamp.try_into()?,
block_number: event.block_number,
tx_hash: felt_to_hex(&event.transaction_hash),
price: None,
};

let tx;
Expand Down
8 changes: 8 additions & 0 deletions apps/indexer/src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ pub struct Request {
pub content: String,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct EventPrice {
pub gas: u64,
pub usd_price: String,
}

/// Records event associated to requests.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Event {
Expand All @@ -107,6 +113,8 @@ pub struct Event {
pub block_number: u64,
// Transaction hash of the transaction which triggered the event.
pub tx_hash: String,
// Transaction price
pub price: Option<EventPrice>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
Expand Down

0 comments on commit 36dd031

Please sign in to comment.