Skip to content

Commit 0c3cd50

Browse files
committed
fix: add missing features
1 parent d4744d6 commit 0c3cd50

2 files changed

Lines changed: 116 additions & 2 deletions

File tree

src/clob/client.rs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ use crate::clob::types::{
5353
CreateRfqRequestRequest, CreateRfqRequestResponse, RfqQuote, RfqQuotesRequest, RfqRequest,
5454
RfqRequestsRequest,
5555
};
56-
use crate::clob::types::{SignableOrder, SignatureType, SignedOrder, TickSize};
56+
use crate::clob::types::{OrderType, Side, SignableOrder, SignatureType, SignedOrder, TickSize};
5757
use crate::error::{Error, Kind as ErrorKind, Synchronization};
58-
use crate::types::{Address, B256};
58+
use crate::types::{Address, B256, Decimal};
5959
use crate::{
6060
AMOY, POLYGON, Result, Timestamp, ToQueryParams as _, auth, contract_config,
6161
derive_proxy_wallet, derive_safe_wallet,
@@ -1227,6 +1227,55 @@ impl<S: State> Client<S> {
12271227
crate::request(&self.inner.client, request, None).await
12281228
}
12291229

1230+
/// Returns raw on-chain trade events for a market condition ID.
1231+
///
1232+
/// # Errors
1233+
///
1234+
/// Returns an error if the request fails or the condition ID is invalid.
1235+
pub async fn market_trades_events(&self, condition_id: &str) -> Result<serde_json::Value> {
1236+
let request = self
1237+
.client()
1238+
.request(
1239+
Method::GET,
1240+
format!("{}markets/live-activity/{condition_id}", self.host()),
1241+
)
1242+
.build()?;
1243+
1244+
crate::request(&self.inner.client, request, None).await
1245+
}
1246+
1247+
/// Calculates the effective fill price for a market order by walking the orderbook.
1248+
///
1249+
/// Fetches the orderbook for `token_id` and delegates to
1250+
/// [`crate::clob::utilities::calculate_market_price`].
1251+
///
1252+
/// # Errors
1253+
///
1254+
/// Returns an error if the orderbook request fails or there is no liquidity
1255+
/// and `order_type` is [`OrderType::FOK`].
1256+
pub async fn calculate_market_price(
1257+
&self,
1258+
token_id: U256,
1259+
side: Side,
1260+
amount: Decimal,
1261+
order_type: OrderType,
1262+
) -> Result<Decimal> {
1263+
let book = self
1264+
.order_book(&OrderBookSummaryRequest {
1265+
token_id,
1266+
side: None,
1267+
})
1268+
.await?;
1269+
1270+
super::utilities::calculate_market_price(&book, side, amount, &order_type).ok_or_else(
1271+
|| {
1272+
Error::validation(format!(
1273+
"Insufficient liquidity to fill {amount} on {side:?} for {token_id}"
1274+
))
1275+
},
1276+
)
1277+
}
1278+
12301279
fn client(&self) -> &ReqwestClient {
12311280
&self.inner.client
12321281
}
@@ -2323,6 +2372,7 @@ impl<K: Kind> Client<Authenticated<K>> {
23232372
metadata: None,
23242373
builder_code: None,
23252374
defer_exec: None,
2375+
user_usdc_balance: None,
23262376
client: Client {
23272377
inner: Arc::clone(&self.inner),
23282378
#[cfg(feature = "heartbeats")]

src/clob/order_builder.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::marker::PhantomData;
22
use std::time::{SystemTime, UNIX_EPOCH};
33

44
use alloy::primitives::{B256, U256};
5+
use alloy::signers::Signer;
56
use chrono::{DateTime, Utc};
67
use rand::RngExt as _;
78
use rust_decimal::prelude::ToPrimitive as _;
@@ -11,6 +12,7 @@ use crate::auth::Kind as AuthKind;
1112
use crate::auth::state::Authenticated;
1213
use crate::clob::Client;
1314
use crate::clob::types::request::OrderBookSummaryRequest;
15+
use crate::clob::types::response::PostOrderResponse;
1416
use crate::clob::types::{
1517
Amount, AmountInner, Order, OrderPayload, OrderType, Side, SignableOrder, SignatureType,
1618
};
@@ -51,6 +53,7 @@ pub struct OrderBuilder<OrderKind, K: AuthKind> {
5153
pub(crate) metadata: Option<B256>,
5254
pub(crate) builder_code: Option<B256>,
5355
pub(crate) defer_exec: Option<bool>,
56+
pub(crate) user_usdc_balance: Option<Decimal>,
5457
pub(crate) _kind: PhantomData<OrderKind>,
5558
}
5659

@@ -269,6 +272,18 @@ impl<K: AuthKind> OrderBuilder<Limit, K> {
269272
defer_exec: self.defer_exec,
270273
})
271274
}
275+
276+
/// Convenience: builds, signs, and posts this limit order in a single call.
277+
///
278+
/// # Errors
279+
///
280+
/// Returns an error if any of the build, sign, or post steps fails.
281+
pub async fn build_sign_and_post<S: Signer>(self, signer: &S) -> Result<PostOrderResponse> {
282+
let client = self.client.clone();
283+
let order = self.build().await?;
284+
let signed = client.sign(signer, order).await?;
285+
client.post_order(signed).await
286+
}
272287
}
273288

274289
impl<K: AuthKind> OrderBuilder<Market, K> {
@@ -286,6 +301,15 @@ impl<K: AuthKind> OrderBuilder<Market, K> {
286301
self
287302
}
288303

304+
/// Sets the user's USDC balance. When set on a BUY market order, `build()` shrinks
305+
/// the USDC amount to cover platform + builder taker fees so the order stays within
306+
/// the user's balance.
307+
#[must_use]
308+
pub fn user_usdc_balance(mut self, balance: Decimal) -> Self {
309+
self.user_usdc_balance = Some(balance);
310+
self
311+
}
312+
289313
// Attempts to calculate the market price from the top of the book for the particular token.
290314
// - Uses an orderbook depth search to find the cutoff price:
291315
// - BUY + USDC: walk asks until notional >= USDC
@@ -408,6 +432,34 @@ impl<K: AuthKind> OrderBuilder<Market, K> {
408432
)));
409433
}
410434

435+
let amount = match (side, amount.0, self.user_usdc_balance) {
436+
(Side::Buy, AmountInner::Usdc(raw), Some(balance))
437+
if matches!(order_type, OrderType::FOK | OrderType::FAK) =>
438+
{
439+
let fee = self.client.fee_rate_bps(token_id).await?;
440+
let fee_rate = Decimal::from(fee.base_fee) / Decimal::from(10_000_u32);
441+
let fee_exponent = Decimal::from(fee.exponent.unwrap_or(0));
442+
let builder_taker_fee = match self.builder_code {
443+
Some(code) if code != B256::ZERO => {
444+
let rate = self.client.builder_fee_rate(code).await?;
445+
Decimal::from(rate.builder_taker_fee_rate_bps) / Decimal::from(10_000_u32)
446+
}
447+
_ => Decimal::ZERO,
448+
};
449+
450+
let adjusted = super::utilities::adjust_market_buy_amount(
451+
raw,
452+
balance,
453+
price,
454+
fee_rate,
455+
fee_exponent,
456+
builder_taker_fee,
457+
);
458+
Amount::usdc(adjusted)?
459+
}
460+
_ => amount,
461+
};
462+
411463
let raw_amount = amount.as_inner();
412464

413465
let (taker_amount, maker_amount) = match (side, amount.0) {
@@ -465,6 +517,18 @@ impl<K: AuthKind> OrderBuilder<Market, K> {
465517
defer_exec: self.defer_exec,
466518
})
467519
}
520+
521+
/// Convenience: builds, signs, and posts this market order in a single call.
522+
///
523+
/// # Errors
524+
///
525+
/// Returns an error if any of the build, sign, or post steps fails.
526+
pub async fn build_sign_and_post<S: Signer>(self, signer: &S) -> Result<PostOrderResponse> {
527+
let client = self.client.clone();
528+
let order = self.build().await?;
529+
let signed = client.sign(signer, order).await?;
530+
client.post_order(signed).await
531+
}
468532
}
469533

470534
/// Removes trailing zeros, truncates to [`USDC_DECIMALS`] decimal places, and quanitizes as an

0 commit comments

Comments
 (0)