Skip to content

Commit 00577c0

Browse files
committed
RAI-753 support inactive order summaries (#138)
## Linear Refs RAI-753: https://linear.app/makeitrain/issue/RAI-753/support-inactiveclosed-orders-in-order-list-endpoints ## Dependencies Stacked on REST API PR: #137 Depends on rain.orderbook PR: rainlanguage/raindex#2688 ## Summary - Add `state=active|inactive|all` to owner and token order list endpoints, defaulting to `active` to preserve current behavior. - Add stable `orderType`, `active`, `removedAt`, and `chainId` fields to order summaries without exposing raw `parsedMeta` or raw Rainlang. - Return inactive order summaries without live quoting: `ioRatio` is `"-"`, `maxOutput` is `null`, and `outputVaultBalance` is `"0"`. - Populate `removedAt` from the rain.orderbook removed timestamp accessor when available, while active orders continue returning `null`. - Update order list docs and route/filter tests for active, inactive, all-state, and chain-scoped summary behavior. ## Checks - `cargo fmt --all -- --check` (rain.orderbook) - `cargo test -p raindex_common raindex_client::orders` (rain.orderbook) - `nix develop -c cargo fmt` - `nix develop -c cargo test routes::orders` - `nix develop -c cargo check` - `nix develop -c rainix-rs-static` - `nix develop -c cargo test`
1 parent ed4a1c6 commit 00577c0

5 files changed

Lines changed: 590 additions & 47 deletions

File tree

docs/src/orders.md

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -146,29 +146,30 @@ response.
146146
## List Orders by Owner
147147

148148
```
149-
GET /v1/orders/{address}
149+
GET /v1/orders/owner/{address}
150150
```
151151

152152
Paginated list of orders for a wallet address.
153153

154154
### Request
155155

156156
```bash
157-
curl "https://api.st0x.io/v1/orders/0xOwnerAddress?page=1&pageSize=10" \
157+
curl "https://api.st0x.io/v1/orders/owner/0xOwnerAddress?state=active&page=1&pageSize=10" \
158158
-H "Authorization: Basic <credentials>"
159159
```
160160

161-
| Parameter | Type | Default | Description |
162-
| -------------- | ------------------------ | --------- | --------------------------------------------------------------------------------------------------------- |
163-
| `page` | number | 1 | Page number |
164-
| `pageSize` | number | 20 | Results per page |
165-
| `denomination` | `wrapped` or `unwrapped` | `wrapped` | Return wrapped token amounts as-is, or normalize wrapped token balances and IO ratios to unwrapped values |
161+
| Parameter | Type | Default | Description |
162+
| -------------- | ------------------------------ | --------- | --------------------------------------------------------------------------------------------------------- |
163+
| `state` | `active`, `inactive`, or `all` | `active` | Filter by current order state |
164+
| `page` | number | 1 | Page number |
165+
| `pageSize` | number | 20 | Results per page |
166+
| `denomination` | `wrapped` or `unwrapped` | `wrapped` | Return wrapped token amounts as-is, or normalize wrapped token balances and IO ratios to unwrapped values |
166167

167168
Use `denomination=unwrapped` to view order balances and IO ratios normalized to
168169
the current unwrapped asset value:
169170

170171
```bash
171-
curl "https://api.st0x.io/v1/orders/0xOwnerAddress?page=1&pageSize=10&denomination=unwrapped" \
172+
curl "https://api.st0x.io/v1/orders/owner/0xOwnerAddress?state=active&page=1&pageSize=10&denomination=unwrapped" \
172173
-H "Authorization: Basic <credentials>"
173174
```
174175

@@ -180,6 +181,11 @@ curl "https://api.st0x.io/v1/orders/0xOwnerAddress?page=1&pageSize=10&denominati
180181
{
181182
"orderHash": "0xabc123...",
182183
"owner": "0xOwnerAddress",
184+
"chainId": 8453,
185+
"orderBytes": "0x...",
186+
"active": true,
187+
"removedAt": null,
188+
"orderType": "limit",
183189
"inputToken": { "address": "0x...", "symbol": "USDC", "decimals": 6 },
184190
"outputToken": { "address": "0x...", "symbol": "WETH", "decimals": 18 },
185191
"outputVaultBalance": "0.5",
@@ -202,6 +208,37 @@ curl "https://api.st0x.io/v1/orders/0xOwnerAddress?page=1&pageSize=10&denominati
202208
`maxOutput` is the quote-derived executable output amount for the listed order.
203209
It is `null` when quote data is unavailable.
204210

211+
`orderType` is one of `limit`, `dca`, `dynamic-spread`, or `custom`.
212+
213+
When `state=inactive`, orders are returned without live quote data: `ioRatio` is
214+
`"-"`, `maxOutput` is `null`, and `outputVaultBalance` is `"0"`. `chainId`,
215+
`orderBytes`, token refs, `orderType`, `active`, and `removedAt` remain
216+
populated when available.
217+
218+
## List Orders by Token
219+
220+
```
221+
GET /v1/orders/token/{address}
222+
```
223+
224+
Paginated list of orders for a token address.
225+
226+
### Request
227+
228+
```bash
229+
curl "https://api.st0x.io/v1/orders/token/0xTokenAddress?state=all&side=output&page=1&pageSize=10" \
230+
-H "Authorization: Basic <credentials>"
231+
```
232+
233+
| Parameter | Type | Default | Description |
234+
| ---------- | ------------------------------ | -------- | ------------------------------------ |
235+
| `state` | `active`, `inactive`, or `all` | `active` | Filter by current order state |
236+
| `side` | `input` or `output` | all | Match token as an input/output token |
237+
| `page` | number | 1 | Page number |
238+
| `pageSize` | number | 20 | Results per page |
239+
240+
The response shape is the same as list orders by owner.
241+
205242
## List Orders by Transaction
206243

207244
```

src/routes/orders/get_by_owner.rs

Lines changed: 85 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
use super::{
2-
build_orders_list_response, current_wrap_ratios_for_orders, OrdersListDataSource,
3-
RaindexOrdersListDataSource, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE,
2+
active_filter_for_state, build_orders_list_response, current_wrap_ratios_for_orders,
3+
get_order_quotes_for_summaries, OrdersListDataSource, RaindexOrdersListDataSource,
4+
DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE,
45
};
56
use crate::app_state::ApplicationState;
67
use crate::auth::AuthenticatedKey;
78
use crate::db::DbPool;
89
use crate::error::{ApiError, ApiErrorResponse};
910
use crate::fairings::{GlobalRateLimit, TracingSpan};
1011
use crate::types::common::{Denomination, ValidatedAddress};
11-
use crate::types::orders::{OrdersListResponse, OrdersPaginationParams};
12+
use crate::types::orders::{OrderState, OrdersListResponse, OrdersPaginationParams};
1213
use alloy::primitives::Address;
1314
use rain_orderbook_common::raindex_client::orders::GetOrdersFilters;
1415
use rocket::serde::json::Json;
@@ -18,14 +19,16 @@ use tracing::Instrument;
1819
pub(crate) async fn process_get_orders_by_owner(
1920
ds: &dyn OrdersListDataSource,
2021
address: Address,
22+
state: Option<OrderState>,
2123
page: Option<u16>,
2224
page_size: Option<u16>,
2325
denomination: Denomination,
2426
) -> Result<OrdersListResponse, ApiError> {
27+
let active_filter = active_filter_for_state(state);
2528
let filters = GetOrdersFilters {
2629
owners: vec![address],
27-
active: Some(true),
28-
has_positive_output_vault_balance: Some(true),
30+
active: active_filter,
31+
has_positive_output_vault_balance: (active_filter == Some(true)).then_some(true),
2932
..Default::default()
3033
};
3134

@@ -41,7 +44,7 @@ pub(crate) async fn process_get_orders_by_owner(
4144
quoted_orders = orders.len(),
4245
"fetching batched quotes for orders by owner"
4346
);
44-
let quote_results = ds.get_order_quotes_batch(&orders).await;
47+
let quote_results = get_order_quotes_for_summaries(ds, &orders).await;
4548
let wrap_ratios = current_wrap_ratios_for_orders(ds, denomination, &orders).await?;
4649

4750
build_orders_list_response(
@@ -88,6 +91,7 @@ pub async fn get_orders_by_address(
8891
async move {
8992
tracing::info!(address = ?address, params = ?params, "request received");
9093
let addr = address.0;
94+
let state = params.state;
9195
let page = params.page;
9296
let page_size = params.page_size;
9397
let denomination = params.denomination.unwrap_or_default();
@@ -98,7 +102,7 @@ pub async fn get_orders_by_address(
98102
pool: pool.inner(),
99103
};
100104
let response =
101-
process_get_orders_by_owner(&ds, addr, page, page_size, denomination).await?;
105+
process_get_orders_by_owner(&ds, addr, state, page, page_size, denomination).await?;
102106
Ok(Json(response))
103107
}
104108
.instrument(span.0)
@@ -111,8 +115,11 @@ mod tests {
111115
use crate::routes::order::test_fixtures::{
112116
mock_order, mock_order_with_shared_vaults, mock_quote,
113117
};
114-
use crate::routes::orders::test_fixtures::MockOrdersListDataSource;
118+
use crate::routes::orders::test_fixtures::{
119+
MockOrdersListDataSource, RecordingOrdersListDataSource,
120+
};
115121
use crate::test_helpers::{basic_auth_header, seed_api_key, TestClientBuilder};
122+
use crate::types::orders::OrderSummaryOrderType;
116123
use rocket::http::{Header, Status};
117124

118125
#[rocket::async_test]
@@ -125,14 +132,19 @@ mod tests {
125132
let addr: Address = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
126133
.parse()
127134
.unwrap();
128-
let result = process_get_orders_by_owner(&ds, addr, None, None, Denomination::Wrapped)
129-
.await
130-
.unwrap();
135+
let result =
136+
process_get_orders_by_owner(&ds, addr, None, None, None, Denomination::Wrapped)
137+
.await
138+
.unwrap();
131139

132140
assert_eq!(result.orders.len(), 1);
133141
assert_eq!(result.orders[0].input_token.symbol, "USDC");
134142
assert_eq!(result.orders[0].output_token.symbol, "WETH");
143+
assert_eq!(result.orders[0].chain_id, 8453);
135144
assert_eq!(result.orders[0].order_bytes.as_ref(), &[1]);
145+
assert!(result.orders[0].active);
146+
assert_eq!(result.orders[0].removed_at, None);
147+
assert_eq!(result.orders[0].order_type, OrderSummaryOrderType::Custom);
136148
assert_eq!(result.orders[0].io_ratio, "1.5");
137149
assert_eq!(result.orders[0].max_output.as_deref(), Some("1"));
138150
assert_eq!(result.pagination.total_orders, 1);
@@ -150,9 +162,10 @@ mod tests {
150162
let addr: Address = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
151163
.parse()
152164
.unwrap();
153-
let result = process_get_orders_by_owner(&ds, addr, None, None, Denomination::Wrapped)
154-
.await
155-
.unwrap();
165+
let result =
166+
process_get_orders_by_owner(&ds, addr, None, None, None, Denomination::Wrapped)
167+
.await
168+
.unwrap();
156169

157170
assert!(result.orders.is_empty());
158171
assert_eq!(result.pagination.total_orders, 0);
@@ -169,9 +182,10 @@ mod tests {
169182
let addr: Address = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
170183
.parse()
171184
.unwrap();
172-
let result = process_get_orders_by_owner(&ds, addr, None, None, Denomination::Wrapped)
173-
.await
174-
.unwrap();
185+
let result =
186+
process_get_orders_by_owner(&ds, addr, None, None, None, Denomination::Wrapped)
187+
.await
188+
.unwrap();
175189

176190
assert_eq!(result.orders[0].io_ratio, "-");
177191
assert_eq!(result.orders[0].max_output, None);
@@ -188,7 +202,7 @@ mod tests {
188202
.parse()
189203
.unwrap();
190204
let result =
191-
process_get_orders_by_owner(&ds, addr, None, None, Denomination::Wrapped).await;
205+
process_get_orders_by_owner(&ds, addr, None, None, None, Denomination::Wrapped).await;
192206
assert!(matches!(result, Err(ApiError::Internal(_))));
193207
}
194208

@@ -202,16 +216,66 @@ mod tests {
202216
let addr: Address = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
203217
.parse()
204218
.unwrap();
205-
let result = process_get_orders_by_owner(&ds, addr, None, None, Denomination::Wrapped)
206-
.await
207-
.unwrap();
219+
let result =
220+
process_get_orders_by_owner(&ds, addr, None, None, None, Denomination::Wrapped)
221+
.await
222+
.unwrap();
208223

209224
assert_eq!(result.orders.len(), 1);
210225
assert_eq!(result.orders[0].input_token.symbol, "wtMSTR");
211226
assert_eq!(result.orders[0].output_token.symbol, "wtMSTR");
227+
assert_eq!(result.orders[0].chain_id, 8453);
212228
assert_eq!(result.orders[0].io_ratio, "200.0");
213229
}
214230

231+
#[rocket::async_test]
232+
async fn test_process_get_orders_by_owner_inactive_state_sets_active_false_filter() {
233+
let ds = RecordingOrdersListDataSource::default();
234+
let addr: Address = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
235+
.parse()
236+
.unwrap();
237+
238+
let result = process_get_orders_by_owner(
239+
&ds,
240+
addr,
241+
Some(OrderState::Inactive),
242+
None,
243+
None,
244+
Denomination::Wrapped,
245+
)
246+
.await;
247+
248+
assert!(result.is_ok());
249+
let filters = ds.filters.lock().expect("lock filters");
250+
assert_eq!(filters.len(), 1);
251+
assert_eq!(filters[0].active, Some(false));
252+
assert_eq!(filters[0].has_positive_output_vault_balance, None);
253+
}
254+
255+
#[rocket::async_test]
256+
async fn test_process_get_orders_by_owner_all_state_omits_active_filter() {
257+
let ds = RecordingOrdersListDataSource::default();
258+
let addr: Address = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
259+
.parse()
260+
.unwrap();
261+
262+
let result = process_get_orders_by_owner(
263+
&ds,
264+
addr,
265+
Some(OrderState::All),
266+
None,
267+
None,
268+
Denomination::Wrapped,
269+
)
270+
.await;
271+
272+
assert!(result.is_ok());
273+
let filters = ds.filters.lock().expect("lock filters");
274+
assert_eq!(filters.len(), 1);
275+
assert_eq!(filters[0].active, None);
276+
assert_eq!(filters[0].has_positive_output_vault_balance, None);
277+
}
278+
215279
#[rocket::async_test]
216280
async fn test_get_orders_by_owner_401_without_auth() {
217281
let client = TestClientBuilder::new().build().await;

0 commit comments

Comments
 (0)