Skip to content
Closed
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ rain_orderbook_js_api = { path = "lib/rain.orderbook/crates/js_api", default-fea
rain_orderbook_common = { path = "lib/rain.orderbook/crates/common", default-features = false }
rain_orderbook_app_settings = { path = "lib/rain.orderbook/crates/settings", default-features = false }
rain_orderbook_bindings = { path = "lib/rain.orderbook/crates/bindings", default-features = false }
rain-math-float = { path = "lib/rain.orderbook/lib/rain.interpreter/lib/rain.interpreter.interface/lib/rain.math.float/crates/float" }
wasm-bindgen = "=0.2.100"
81 changes: 0 additions & 81 deletions src/routes/swap.rs

This file was deleted.

41 changes: 41 additions & 0 deletions src/routes/swap/calldata.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use crate::auth::AuthenticatedKey;
use crate::error::{ApiError, ApiErrorResponse};
use crate::fairings::{GlobalRateLimit, TracingSpan};
use crate::types::swap::{SwapCalldataRequest, SwapCalldataResponse};
use rocket::serde::json::Json;
use rocket::State;
use tracing::Instrument;

#[utoipa::path(
post,
path = "/v1/swap/calldata",
tag = "Swap",
security(("basicAuth" = [])),
request_body = SwapCalldataRequest,
responses(
(status = 200, description = "Swap calldata", body = SwapCalldataResponse),
(status = 400, description = "Bad request", body = ApiErrorResponse),
(status = 401, description = "Unauthorized", body = ApiErrorResponse),
(status = 429, description = "Rate limited", body = ApiErrorResponse),
(status = 500, description = "Internal server error", body = ApiErrorResponse),
)
)]
#[post("/calldata", data = "<request>")]
pub async fn post_swap_calldata(
_global: GlobalRateLimit,
_key: AuthenticatedKey,
raindex: &State<crate::raindex::RaindexProvider>,
span: TracingSpan,
request: Json<SwapCalldataRequest>,
) -> Result<Json<SwapCalldataResponse>, ApiError> {
let req = request.into_inner();
async move {
tracing::info!(body = ?req, "request received");
raindex
.run_with_client(move |_client| async move { todo!() })
.await
.map_err(ApiError::from)?
Comment on lines +34 to +37
Copy link

@coderabbitai coderabbitai bot Feb 16, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

todo!() will panic at runtime on a registered route.

This route is registered in routes() (see src/routes/swap/mod.rs line 79) and is reachable by any authenticated user. The todo!() macro causes an unrecoverable panic, which will crash the request handler (and potentially the worker thread depending on Rocket's configuration).

Either:

  1. Don't register the route until it's implemented, or
  2. Return a proper error response (e.g., 501 Not Implemented).
Option 1: Remove from routes until ready

In src/routes/swap/mod.rs:

 pub fn routes() -> Vec<Route> {
-    rocket::routes![quote::post_swap_quote, calldata::post_swap_calldata]
+    rocket::routes![quote::post_swap_quote]
 }
Option 2: Return a proper error instead of panicking
     raindex
-        .run_with_client(move |_client| async move { todo!() })
-        .await
-        .map_err(ApiError::from)?
+        .run_with_client(move |_client| async move {
+            Err::<Json<SwapCalldataResponse>, ApiError>(
+                ApiError::Internal("calldata endpoint not yet implemented".into()),
+            )
+        })
+        .await
+        .map_err(ApiError::from)?
🤖 Prompt for AI Agents
In `@src/routes/swap/calldata.rs` around lines 34 - 37, The current handler using
raindex.run_with_client(move |_client| async move { todo!() }) will panic at
runtime; either remove this route registration from routes() until implemented,
or replace the todo!() with an async branch that returns a proper ApiError
mapped to HTTP 501 Not Implemented (so the .map_err(ApiError::from)? path still
composes). Locate the call inside calldata.rs (the raindex.run_with_client
closure) and either delete the route registration in routes() (so it is not
reachable) or change the closure to return an Err(ApiError) that represents "Not
Implemented" instead of calling todo!(). Ensure you reference
raindex.run_with_client and the surrounding .map_err(ApiError::from)? when
implementing the fix.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is fine for now as we are not live yet. We are doing these one by one.

Copy link

Choose a reason for hiding this comment

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

@findolor, understood! That makes sense for incremental development. I'll note that the endpoint is pending implementation and won't flag similar patterns while you're working through the endpoints one by one.

(•ᴗ•)

}
.instrument(span.0)
.await
}
149 changes: 149 additions & 0 deletions src/routes/swap/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
mod calldata;
mod quote;

use crate::error::ApiError;
use alloy::primitives::Address;
use async_trait::async_trait;
use rain_orderbook_common::raindex_client::orders::{
GetOrdersFilters, GetOrdersTokenFilter, RaindexOrder,
};
use rain_orderbook_common::raindex_client::RaindexClient;
use rain_orderbook_common::take_orders::{
build_take_order_candidates_for_pair, TakeOrderCandidate,
};
use rocket::Route;

#[async_trait(?Send)]
pub(crate) trait SwapDataSource {
async fn get_orders_for_pair(
&self,
input_token: Address,
output_token: Address,
) -> Result<Vec<RaindexOrder>, ApiError>;

async fn build_candidates_for_pair(
&self,
orders: &[RaindexOrder],
input_token: Address,
output_token: Address,
) -> Result<Vec<TakeOrderCandidate>, ApiError>;
}

pub(crate) struct RaindexSwapDataSource<'a> {
pub client: &'a RaindexClient,
}

#[async_trait(?Send)]
impl<'a> SwapDataSource for RaindexSwapDataSource<'a> {
async fn get_orders_for_pair(
&self,
input_token: Address,
output_token: Address,
) -> Result<Vec<RaindexOrder>, ApiError> {
let filters = GetOrdersFilters {
active: Some(true),
tokens: Some(GetOrdersTokenFilter {
inputs: Some(vec![input_token]),
outputs: Some(vec![output_token]),
}),
..Default::default()
};
self.client
.get_orders(None, Some(filters), None)
.await
.map_err(|e| {
tracing::error!(error = %e, "failed to query orders for pair");
ApiError::Internal("failed to query orders".into())
})
}

async fn build_candidates_for_pair(
&self,
orders: &[RaindexOrder],
input_token: Address,
output_token: Address,
) -> Result<Vec<TakeOrderCandidate>, ApiError> {
build_take_order_candidates_for_pair(orders, input_token, output_token, None, None)
.await
.map_err(|e| {
tracing::error!(error = %e, "failed to build order candidates");
ApiError::Internal("failed to build order candidates".into())
})
}
}

pub use calldata::*;
pub use quote::*;

pub fn routes() -> Vec<Route> {
rocket::routes![quote::post_swap_quote, calldata::post_swap_calldata]
}

#[cfg(test)]
pub(crate) mod test_fixtures {
use super::SwapDataSource;
use crate::error::ApiError;
use alloy::primitives::{Address, U256};
use async_trait::async_trait;
use rain_math_float::Float;
use rain_orderbook_bindings::IOrderBookV6::{EvaluableV4, OrderV4, IOV2};
use rain_orderbook_common::raindex_client::orders::RaindexOrder;
use rain_orderbook_common::take_orders::TakeOrderCandidate;

pub struct MockSwapDataSource {
pub orders: Result<Vec<RaindexOrder>, ApiError>,
pub candidates: Vec<TakeOrderCandidate>,
}

#[async_trait(?Send)]
impl SwapDataSource for MockSwapDataSource {
async fn get_orders_for_pair(
&self,
_input_token: Address,
_output_token: Address,
) -> Result<Vec<RaindexOrder>, ApiError> {
match &self.orders {
Ok(orders) => Ok(orders.clone()),
Err(_) => Err(ApiError::Internal("failed to query orders".into())),
}
}

async fn build_candidates_for_pair(
&self,
_orders: &[RaindexOrder],
_input_token: Address,
_output_token: Address,
) -> Result<Vec<TakeOrderCandidate>, ApiError> {
Ok(self.candidates.clone())
}
}

pub fn mock_candidate(max_output: &str, ratio: &str) -> TakeOrderCandidate {
let token_a = Address::from([4u8; 20]);
let token_b = Address::from([5u8; 20]);
TakeOrderCandidate {
orderbook: Address::from([0xAAu8; 20]),
order: OrderV4 {
owner: Address::from([1u8; 20]),
nonce: U256::from(1).into(),
evaluable: EvaluableV4 {
interpreter: Address::from([2u8; 20]),
store: Address::from([3u8; 20]),
bytecode: alloy::primitives::Bytes::from(vec![0x01, 0x02]),
},
validInputs: vec![IOV2 {
token: token_a,
vaultId: U256::from(100).into(),
}],
validOutputs: vec![IOV2 {
token: token_b,
vaultId: U256::from(200).into(),
}],
},
input_io_index: 0,
output_io_index: 0,
max_output: Float::parse(max_output.to_string()).unwrap(),
ratio: Float::parse(ratio.to_string()).unwrap(),
}
}
}
Loading