Skip to content

Commit 222b3f5

Browse files
authored
Feat/cu price (#47)
* Allow setting CU price per request * Refactor controller methods to receive context struct
1 parent b650d77 commit 222b3f5

File tree

3 files changed

+60
-73
lines changed

3 files changed

+60
-73
lines changed

README.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,11 @@ A `subAccountId` URL query parameter may be supplied to switch the sub-account p
121121

122122
e.g `http://<gateway>/v1/orders?subAccountId=3` will return orders for the wallet's sub-account 3
123123

124-
## CU Limits
124+
## CU Price & Limits
125125

126-
CU limit may be set on transaction request with the query parameter `computeUnitLimit=300000`, the default if unset is `200000`.
126+
**CU limit** may be set on transaction request with the query parameter `computeUnitLimit=300000`, the default if unset is `200000`.
127+
128+
**CU price** in micro-lamports may be set on transaction request with the query parameter `computeUnitPrice=1000`, the default if unset is a dynamic value from chain set at 90-th percentile of the local fee market.
127129

128130
The following error is logged when a tx does not have enough CU limit, increasing the cu limit can fix it or reducing number complexity of the order e..g number of orders/markets per batch.
129131

@@ -134,13 +136,11 @@ The following error is logged when a tx does not have enough CU limit, increasin
134136
**example request**
135137

136138
```bash
137-
$ curl 'localhost:8080/v2/orders?computeUnitLimit=300000' -X POST \
139+
$ curl 'localhost:8080/v2/orders?computeUnitLimit=300000&computeUnitPrice=1000' -X POST \
138140
-H 'content-type: application/json' \
139141
-d # { order data ...}
140142
```
141143

142-
The CU price (aka priority fee) is automatically inferred from onchain fee data at 75th percentile.
143-
144144
## API Examples
145145

146146
Please refer to https://drift-labs.github.io/v2-teacher/ for further examples and reference documentation on various types, fields, and operations available on drift.

src/controller.rs

+32-32
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use drift_sdk::{
44
event_subscriber::{try_parse_log, CommitmentConfig},
55
math::liquidation::calculate_liquidation_price_and_unrealized_pnl,
66
types::{
7-
Context, MarketId, MarketType, ModifyOrderParams, RpcSendTransactionConfig, SdkError,
7+
self, MarketId, MarketType, ModifyOrderParams, RpcSendTransactionConfig, SdkError,
88
SdkResult, VersionedMessage,
99
},
1010
AccountProvider, DriftClient, Pubkey, RpcAccountProvider, TransactionBuilder, Wallet,
@@ -15,7 +15,7 @@ use rust_decimal::Decimal;
1515
use solana_client::{client_error::ClientErrorKind, rpc_config::RpcTransactionConfig};
1616
use solana_sdk::signature::Signature;
1717
use solana_transaction_status::{option_serializer::OptionSerializer, UiTransactionEncoding};
18-
use std::{borrow::Cow, str::FromStr, sync::Arc, time::Duration};
18+
use std::{borrow::Cow, str::FromStr, sync::Arc};
1919
use thiserror::Error;
2020

2121
use crate::{
@@ -27,7 +27,7 @@ use crate::{
2727
TxEventsResponse, TxResponse, PRICE_DECIMALS,
2828
},
2929
websocket::map_drift_event_for_account,
30-
LOG_TARGET,
30+
Context, LOG_TARGET,
3131
};
3232

3333
pub type GatewayResult<T> = Result<T, ControllerError>;
@@ -84,9 +84,9 @@ impl AppState {
8484
let (state_commitment, tx_commitment) =
8585
commitment.unwrap_or((CommitmentConfig::confirmed(), CommitmentConfig::confirmed()));
8686
let context = if devnet {
87-
Context::DevNet
87+
types::Context::DevNet
8888
} else {
89-
Context::MainNet
89+
types::Context::MainNet
9090
};
9191

9292
let account_provider = RpcAccountProvider::with_commitment(endpoint, state_commitment);
@@ -132,35 +132,36 @@ impl AppState {
132132
/// 4) catch all. cancel all orders
133133
pub async fn cancel_orders(
134134
&self,
135+
ctx: Context,
135136
req: CancelOrdersRequest,
136-
sub_account_id: Option<u16>,
137-
cu_limit: Option<u32>,
138137
) -> GatewayResult<TxResponse> {
139-
let sub_account = self.resolve_sub_account(sub_account_id);
138+
let sub_account = self.resolve_sub_account(ctx.sub_account_id);
140139
let (account_data, pf) = tokio::join!(
141140
self.client.get_user_account(&sub_account),
142141
get_priority_fee(&self.client)
143142
);
143+
let priority_fee = ctx.cu_price.unwrap_or(pf);
144+
debug!(target: LOG_TARGET, "priority_fee: {priority_fee:?}");
144145
let builder = TransactionBuilder::new(
145146
self.client.program_data(),
146147
sub_account,
147148
Cow::Owned(account_data?),
148149
self.delegated,
149150
)
150-
.with_priority_fee(pf, cu_limit);
151+
.with_priority_fee(priority_fee, ctx.cu_limit);
151152
let tx = build_cancel_ix(builder, req)?.build();
152153
self.send_tx(tx, "cancel_orders").await
153154
}
154155

155156
/// Return orders by position if given, otherwise return all positions
156157
pub async fn get_positions(
157158
&self,
159+
ctx: Context,
158160
req: Option<GetPositionsRequest>,
159-
sub_account_id: Option<u16>,
160161
) -> GatewayResult<GetPositionsResponse> {
161162
let (all_spot, all_perp) = self
162163
.client
163-
.all_positions(&self.resolve_sub_account(sub_account_id))
164+
.all_positions(&self.resolve_sub_account(ctx.sub_account_id))
164165
.await?;
165166

166167
// calculating spot token balance requires knowing the 'spot market account' data
@@ -204,10 +205,10 @@ impl AppState {
204205

205206
pub async fn get_position_extended(
206207
&self,
207-
sub_account_id: Option<u16>,
208+
ctx: Context,
208209
market: Market,
209210
) -> GatewayResult<PerpPosition> {
210-
let sub_account = self.resolve_sub_account(sub_account_id);
211+
let sub_account = self.resolve_sub_account(ctx.sub_account_id);
211212
let (perp_position, user, oracle) = tokio::join!(
212213
self.client.perp_position(&sub_account, market.market_index),
213214
self.client.get_user_account(&sub_account),
@@ -247,10 +248,10 @@ impl AppState {
247248
/// Return orders by market if given, otherwise return all orders
248249
pub async fn get_orders(
249250
&self,
251+
ctx: Context,
250252
req: Option<GetOrdersRequest>,
251-
sub_account_id: Option<u16>,
252253
) -> GatewayResult<GetOrdersResponse> {
253-
let sub_account = self.resolve_sub_account(sub_account_id);
254+
let sub_account = self.resolve_sub_account(ctx.sub_account_id);
254255
let orders = self.client.all_orders(&sub_account).await?;
255256
Ok(GetOrdersResponse {
256257
orders: orders
@@ -299,9 +300,8 @@ impl AppState {
299300

300301
pub async fn cancel_and_place_orders(
301302
&self,
303+
ctx: Context,
302304
req: CancelAndPlaceRequest,
303-
sub_account_id: Option<u16>,
304-
cu_limit: Option<u32>,
305305
) -> GatewayResult<TxResponse> {
306306
let orders = req
307307
.place
@@ -313,7 +313,7 @@ impl AppState {
313313
})
314314
.collect();
315315

316-
let sub_account = self.resolve_sub_account(sub_account_id);
316+
let sub_account = self.resolve_sub_account(ctx.sub_account_id);
317317
let (account_data, pf) = tokio::join!(
318318
self.client.get_user_account(&sub_account),
319319
get_priority_fee(&self.client)
@@ -325,7 +325,7 @@ impl AppState {
325325
Cow::Owned(account_data?),
326326
self.delegated,
327327
)
328-
.with_priority_fee(pf, cu_limit);
328+
.with_priority_fee(ctx.cu_price.unwrap_or(pf), ctx.cu_limit);
329329

330330
let builder = build_cancel_ix(builder, req.cancel)?;
331331
let tx = build_modify_ix(builder, req.modify, self.client.program_data())?
@@ -337,16 +337,18 @@ impl AppState {
337337

338338
pub async fn place_orders(
339339
&self,
340+
ctx: Context,
340341
req: PlaceOrdersRequest,
341-
sub_account_id: Option<u16>,
342-
cu_limit: Option<u32>,
343342
) -> GatewayResult<TxResponse> {
344-
let sub_account = self.resolve_sub_account(sub_account_id);
343+
let sub_account = self.resolve_sub_account(ctx.sub_account_id);
345344
let (account_data, pf) = tokio::join!(
346345
self.client.get_user_account(&sub_account),
347346
get_priority_fee(&self.client)
348347
);
349348

349+
let priority_fee = ctx.cu_price.unwrap_or(pf);
350+
debug!(target: LOG_TARGET, "priority fee: {priority_fee:?}");
351+
350352
let orders = req
351353
.orders
352354
.into_iter()
@@ -361,7 +363,7 @@ impl AppState {
361363
Cow::Owned(account_data?),
362364
self.delegated,
363365
)
364-
.with_priority_fee(pf, cu_limit)
366+
.with_priority_fee(priority_fee, ctx.cu_limit)
365367
.place_orders(orders)
366368
.build();
367369

@@ -370,11 +372,10 @@ impl AppState {
370372

371373
pub async fn modify_orders(
372374
&self,
375+
ctx: Context,
373376
req: ModifyOrdersRequest,
374-
sub_account_id: Option<u16>,
375-
cu_limit: Option<u32>,
376377
) -> GatewayResult<TxResponse> {
377-
let sub_account = self.resolve_sub_account(sub_account_id);
378+
let sub_account = self.resolve_sub_account(ctx.sub_account_id);
378379
let (account_data, pf) = tokio::join!(
379380
self.client.get_user_account(&sub_account),
380381
get_priority_fee(&self.client)
@@ -386,7 +387,7 @@ impl AppState {
386387
Cow::Owned(account_data?),
387388
self.delegated,
388389
)
389-
.with_priority_fee(pf, cu_limit);
390+
.with_priority_fee(ctx.cu_price.unwrap_or(pf), ctx.cu_limit);
390391
let tx = build_modify_ix(builder, req, self.client.program_data())?.build();
391392
self.send_tx(tx, "modify_orders").await
392393
}
@@ -402,8 +403,8 @@ impl AppState {
402403

403404
pub async fn get_tx_events_for_subaccount_id(
404405
&self,
406+
ctx: Context,
405407
tx_sig: &str,
406-
sub_account_id: Option<u16>,
407408
) -> GatewayResult<TxEventsResponse> {
408409
let signature = Signature::from_str(tx_sig).map_err(|err| {
409410
warn!(target: LOG_TARGET, "failed to parse transaction signature: {err:?}");
@@ -432,7 +433,7 @@ impl AppState {
432433
if let Some(meta) = tx.transaction.meta {
433434
match meta.log_messages {
434435
OptionSerializer::Some(logs) => {
435-
let sub_account = self.resolve_sub_account(sub_account_id);
436+
let sub_account = self.resolve_sub_account(ctx.sub_account_id);
436437
for (tx_idx, log) in logs.iter().enumerate() {
437438
if let Some(evt) = try_parse_log(log.as_str(), tx_sig, tx_idx) {
438439
let (_, gw_event) = map_drift_event_for_account(
@@ -508,7 +509,6 @@ impl AppState {
508509
let client = Arc::clone(&self.client);
509510
let commitment = self.tx_commitment.commitment;
510511
tokio::spawn(async move {
511-
tokio::time::sleep(Duration::from_millis(200)).await;
512512
if let Err(err) = client
513513
.inner()
514514
.send_transaction_with_config(
@@ -641,7 +641,7 @@ async fn get_priority_fee<T: AccountProvider>(client: &DriftClient<T>) -> u64 {
641641
.await
642642
{
643643
recent_fees.sort_unstable();
644-
let idx = (recent_fees.len() * 3) / 4; // 75-th percentile
644+
let idx = (recent_fees.len() * 90) / 100; // 90-th percentile
645645
priority_fee = recent_fees[idx];
646646
} else {
647647
warn!(target: "controller", "failed to fetch live priority fee");
@@ -660,7 +660,7 @@ mod tests {
660660
// flakey needs a mainnet RPC getProgramAccounts
661661
let account_provider = RpcAccountProvider::new("https://api.devnet.solana.com");
662662
let client = DriftClient::new(
663-
Context::DevNet,
663+
types::Context::DevNet,
664664
account_provider,
665665
Wallet::read_only(Pubkey::new_unique()),
666666
)

0 commit comments

Comments
 (0)