Skip to main content

hypercall_api/handlers/
gas_provider.rs

1use axum::{
2    extract::{Path, State},
3    response::IntoResponse,
4};
5use serde::Serialize;
6use utoipa::ToSchema;
7
8use super::AppState;
9use crate::error::ApiError;
10use crate::gas_provider::{format_wei_as_gwei_string, GasEstimateResult, GasEstimateTier};
11use crate::sonic_json::SonicJson;
12
13#[derive(Debug, Serialize, ToSchema)]
14pub struct GasProviderFeeTierResponse {
15    #[schema(value_type = String, example = "1.2")]
16    #[serde(rename = "suggestedMaxPriorityFeePerGas")]
17    pub suggested_max_priority_fee_per_gas: String,
18    #[schema(value_type = String, example = "120")]
19    #[serde(rename = "suggestedMaxFeePerGas")]
20    pub suggested_max_fee_per_gas: String,
21}
22
23impl GasProviderFeeTierResponse {
24    fn from_tier(tier: GasEstimateTier) -> Result<Self, String> {
25        Ok(Self {
26            suggested_max_priority_fee_per_gas: format_wei_as_gwei_string(
27                tier.max_priority_fee_wei,
28            )?,
29            suggested_max_fee_per_gas: format_wei_as_gwei_string(tier.max_fee_wei)?,
30        })
31    }
32}
33
34#[derive(Debug, Serialize, ToSchema)]
35pub struct GasProviderResponse {
36    pub slow: GasProviderFeeTierResponse,
37    pub medium: GasProviderFeeTierResponse,
38    pub fast: GasProviderFeeTierResponse,
39    #[serde(rename = "superFast")]
40    pub super_fast: GasProviderFeeTierResponse,
41}
42
43impl GasProviderResponse {
44    fn from_estimates(estimates: GasEstimateResult) -> Result<Self, String> {
45        Ok(Self {
46            slow: GasProviderFeeTierResponse::from_tier(estimates.slow)?,
47            medium: GasProviderFeeTierResponse::from_tier(estimates.medium)?,
48            fast: GasProviderFeeTierResponse::from_tier(estimates.fast)?,
49            super_fast: GasProviderFeeTierResponse::from_tier(estimates.super_fast)?,
50        })
51    }
52}
53
54#[derive(Debug, Serialize, ToSchema)]
55pub struct GasProviderErrorResponse {
56    #[schema(value_type = String, example = "not_found")]
57    pub error: String,
58    #[schema(value_type = String, example = "Gas provider only supports chain_id 1")]
59    pub message: String,
60}
61
62/// Return gas fee estimates for the configured EVM RPC chain.
63#[utoipa::path(
64    get,
65    path = "/gas-provider/{chain_id}",
66    params(("chain_id" = u64, Path, description = "Startup-resolved chain id supported by this gas provider")),
67    responses(
68        (status = 200, description = "Gas fee estimates", body = GasProviderResponse),
69        (status = 404, description = "Unsupported chain id", body = GasProviderErrorResponse),
70        (status = 500, description = "Upstream RPC fee estimation failed", body = GasProviderErrorResponse)
71    ),
72    tag = "Markets"
73)]
74pub async fn get_gas_provider(
75    State(state): State<AppState>,
76    Path(chain_id): Path<u64>,
77) -> Result<impl IntoResponse, ApiError> {
78    let supported_chain_id = state.gas_provider_service.chain_id();
79    if chain_id != supported_chain_id {
80        return Err(ApiError::not_found(format!(
81            "Gas provider only supports chain_id {}",
82            supported_chain_id
83        )));
84    }
85
86    let estimates = state
87        .gas_provider_service
88        .estimate_fees()
89        .await
90        .map_err(|err| ApiError::internal_error(format!("Failed to estimate gas fees: {err}")))?;
91    let response = GasProviderResponse::from_estimates(estimates)
92        .map_err(|err| ApiError::internal_error(format!("Failed to serialize gas fees: {err}")))?;
93
94    Ok(SonicJson(response))
95}