Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Retrieve L1 deposit transaction price #192

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
517 changes: 465 additions & 52 deletions apps/indexer/Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions apps/indexer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ log = "0.4.17"
num-bigint = "0.4.4"
config = { version = "0.14.0", features = ["json"] }
sha3 = "0.10.8"
reqwest = { version = "0.12.3", features = ["json"] }
22 changes: 21 additions & 1 deletion apps/indexer/src/ethereum_indexer/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,30 @@ impl EthereumClient {
Ok(logs)
}

/// Retrieve message status in StarknetCore messaging contract
pub async fn query_message_status(&self, msg_hash: [u8; 32]) -> Result<u64> {
let messaging =
StarknetMessaging::new(self.messaging_address, Arc::new(self.provider.clone()));
let status = messaging.l2_to_l1_messages(msg_hash).call().await?;
Ok(status.try_into().unwrap())
match status.try_into() {
Ok(s) => Ok(s),
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<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)),
}
} 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C'est le même contract address sur sepolia (pour eth) ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moralis fournit le prix d'un token en s'appuyant sur uniswap v3 (mainnet) et c'est l'adresse du WETH.
Lorsque l'indexeur tourne sur sepolia, il recevra toujours le prix du WETH de la pool uniswap, ça ne devrait pas poser de soucis.

.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
Loading