use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use ethers_core::types::transaction::eip2718::TypedTransaction;
use ethers_core::types::{Address, Bytes};
use ethers_core::utils::hex;
use ethers_providers::Middleware;
use reqwest::Response;
use serde::Deserialize;
use crate::errors::{CCIPFetchError, CCIPRequestError};
use crate::utils::truncate_str;
use crate::CCIPReadMiddlewareError;
use crate::CCIPRequest;
#[derive(Debug, Clone, Deserialize)]
pub struct CCIPResponse {
pub data: Option<String>,
pub message: Option<String>,
}
pub async fn handle_ccip_raw(
client: &reqwest::Client,
url: &str,
sender: &Address,
calldata: &[u8],
) -> Result<Bytes, CCIPRequestError> {
tracing::debug!("making CCIP request to {url}");
let sender_hex = hex::encode_prefixed(sender.0);
let data_hex: String = hex::encode_prefixed(calldata);
tracing::debug!("sender: {}", sender_hex);
tracing::debug!("data: {}", truncate_str(&data_hex, 20));
let request = if url.contains("{data}") {
let href = url
.replace("{sender}", &sender_hex)
.replace("{data}", &data_hex);
client.get(href)
} else {
let body = serde_json::json!({
"data": data_hex,
"sender": sender_hex
});
client.post(url).json(&body)
};
let resp: Response = request.send().await?;
let resp_text = resp.text().await?;
let result: CCIPResponse = serde_json::from_str(&resp_text).map_err(|err| {
CCIPRequestError::GatewayFormatError(format!(
"response format error: {err}, gateway returned: {resp_text}"
))
})?;
if let Some(response_data) = result.data {
return hex::decode(response_data)
.map_err(|_| {
CCIPRequestError::GatewayFormatError(
"response data is not a valid hex sequence".to_string(),
)
})
.map(Bytes::from);
};
if let Some(message) = result.message {
return Err(CCIPRequestError::GatewayError(message));
}
Err(CCIPRequestError::GatewayFormatError(
"response format error: invalid response".to_string(),
))
}
pub async fn handle_ccip<M: Middleware>(
client: &reqwest::Client,
sender: &Address,
tx: &TypedTransaction,
calldata: &[u8],
urls: Vec<String>,
) -> Result<(Bytes, Vec<CCIPRequest>), CCIPReadMiddlewareError<M>> {
if urls.is_empty() || tx.to().is_none() {
return Ok((Bytes::new(), Vec::new()));
}
let urls = dedup_ord(&urls);
let mut errors: HashMap<String, Vec<String>> = HashMap::new();
let mut requests = Vec::new();
for url in urls {
let result = handle_ccip_raw(client, &url, sender, calldata).await;
requests.push(CCIPRequest {
url: url.clone(),
sender: *sender,
calldata: calldata.to_vec().into(),
});
match result {
Ok(result) => return Ok((result, requests)),
Err(err) => {
errors.entry(url).or_default().push(err.to_string());
}
}
}
Err(CCIPReadMiddlewareError::FetchError(CCIPFetchError(errors)))
}
fn dedup_ord<T: Clone + Hash + Eq>(src: &[T]) -> Vec<T> {
let mut set = HashSet::new();
let mut copy = src.to_vec();
copy.retain(|item| set.insert(item.clone()));
copy
}