Skip to main content

maxt_bindings_common/
foreign.rs

1use std::collections::HashSet;
2use std::sync::Arc;
3
4use maxt::{
5    AccountStream, Adapter, AssetNetwork, Balance, BoxFuture, CancelOrdersRequest,
6    CancelOrdersResult, Candle, CandleRequest, Deposit, DepositAddress, DepositAddressEntry,
7    DepositAddressRequest, Exchange, Feature, FundingPayment, FundingRate, HistoryRequest,
8    MarginRequest, MarginSummary, Market, MarketInfo, MarketKind, MarketStream, Order, OrderBook,
9    OrderHistoryRequest, OrderLookupRequest, OrderRequest, OrderRules, Page, Position, Result,
10    StreamConfig, Subscription, Ticker, Trade, TransferHistoryRequest, TransferLookupRequest,
11    WithdrawRequest, Withdrawal, WithdrawalQuote,
12};
13
14use crate::{AdapterCall, AdapterReply, ForeignDispatcher};
15
16/// A Rust [`Adapter`] backed by a [`ForeignDispatcher`].
17pub struct ForeignAdapter {
18    exchange: Exchange,
19    features: HashSet<Feature>,
20    dispatcher: Arc<dyn ForeignDispatcher>,
21}
22
23impl ForeignAdapter {
24    /// Creates an adapter with binding-owned exchange and feature metadata.
25    pub fn new(
26        exchange: Exchange,
27        features: impl IntoIterator<Item = Feature>,
28        dispatcher: Arc<dyn ForeignDispatcher>,
29    ) -> Self {
30        Self {
31            exchange,
32            features: features.into_iter().collect(),
33            dispatcher,
34        }
35    }
36
37    /// The configured features without duplicates.
38    pub fn features(&self) -> &HashSet<Feature> {
39        &self.features
40    }
41
42    /// The binding-specific dispatcher.
43    pub fn dispatcher(&self) -> &dyn ForeignDispatcher {
44        self.dispatcher.as_ref()
45    }
46}
47
48macro_rules! dispatch {
49    ($self:expr, $call:expr, $variant:path, $expected:literal) => {{
50        let future = $self.dispatcher.dispatch($call);
51        Box::pin(async move {
52            match future.await? {
53                $variant(value) => Ok(value),
54                reply => Err(unexpected_reply($expected, &reply)),
55            }
56        })
57    }};
58}
59
60impl Adapter for ForeignAdapter {
61    fn exchange(&self) -> Exchange {
62        self.exchange
63    }
64
65    fn supports(&self, feature: Feature) -> bool {
66        self.features.contains(&feature)
67    }
68
69    fn markets(&self, kind: MarketKind) -> BoxFuture<'_, Result<Vec<MarketInfo>>> {
70        dispatch!(
71            self,
72            AdapterCall::Markets { kind },
73            AdapterReply::Markets,
74            "Markets"
75        )
76    }
77
78    fn trades(&self, market: &Market, limit: Option<u32>) -> BoxFuture<'_, Result<Vec<Trade>>> {
79        dispatch!(
80            self,
81            AdapterCall::Trades {
82                market: market.clone(),
83                limit,
84            },
85            AdapterReply::Trades,
86            "Trades"
87        )
88    }
89
90    fn order_book(&self, market: &Market, depth: Option<u32>) -> BoxFuture<'_, Result<OrderBook>> {
91        dispatch!(
92            self,
93            AdapterCall::OrderBook {
94                market: market.clone(),
95                depth,
96            },
97            AdapterReply::OrderBook,
98            "OrderBook"
99        )
100    }
101
102    fn ticker(&self, market: &Market) -> BoxFuture<'_, Result<Ticker>> {
103        dispatch!(
104            self,
105            AdapterCall::Ticker {
106                market: market.clone(),
107            },
108            AdapterReply::Ticker,
109            "Ticker"
110        )
111    }
112
113    fn candles(&self, request: &CandleRequest) -> BoxFuture<'_, Result<Vec<Candle>>> {
114        dispatch!(
115            self,
116            AdapterCall::Candles {
117                request: request.clone(),
118            },
119            AdapterReply::Candles,
120            "Candles"
121        )
122    }
123
124    fn subscribe(
125        &self,
126        subscription: &Subscription,
127        config: &StreamConfig,
128    ) -> BoxFuture<'_, Result<MarketStream>> {
129        dispatch!(
130            self,
131            AdapterCall::Subscribe {
132                subscription: subscription.clone(),
133                config: config.clone(),
134            },
135            AdapterReply::MarketStream,
136            "MarketStream"
137        )
138    }
139
140    fn balances(&self) -> BoxFuture<'_, Result<Vec<Balance>>> {
141        dispatch!(
142            self,
143            AdapterCall::Balances,
144            AdapterReply::Balances,
145            "Balances"
146        )
147    }
148
149    fn order_rules(&self, market: &Market) -> BoxFuture<'_, Result<OrderRules>> {
150        let future = self.dispatcher.dispatch(AdapterCall::OrderRules {
151            market: market.clone(),
152        });
153        Box::pin(async move {
154            match future.await? {
155                AdapterReply::OrderRules(value) => Ok(*value),
156                reply => Err(unexpected_reply("OrderRules", &reply)),
157            }
158        })
159    }
160
161    fn asset_networks(&self, asset: &str) -> BoxFuture<'_, Result<Vec<AssetNetwork>>> {
162        dispatch!(
163            self,
164            AdapterCall::AssetNetworks {
165                asset: asset.to_owned(),
166            },
167            AdapterReply::AssetNetworks,
168            "AssetNetworks"
169        )
170    }
171
172    fn deposit_addresses(&self) -> BoxFuture<'_, Result<Vec<DepositAddressEntry>>> {
173        dispatch!(
174            self,
175            AdapterCall::DepositAddresses,
176            AdapterReply::DepositAddresses,
177            "DepositAddresses"
178        )
179    }
180
181    fn deposit_address(
182        &self,
183        request: &DepositAddressRequest,
184    ) -> BoxFuture<'_, Result<DepositAddress>> {
185        dispatch!(
186            self,
187            AdapterCall::DepositAddress {
188                request: request.clone(),
189            },
190            AdapterReply::DepositAddress,
191            "DepositAddress"
192        )
193    }
194
195    fn create_deposit_address(
196        &self,
197        request: &DepositAddressRequest,
198    ) -> BoxFuture<'_, Result<DepositAddress>> {
199        dispatch!(
200            self,
201            AdapterCall::CreateDepositAddress {
202                request: request.clone(),
203            },
204            AdapterReply::CreateDepositAddress,
205            "CreateDepositAddress"
206        )
207    }
208
209    fn prepare_withdrawal(
210        &self,
211        request: &WithdrawRequest,
212    ) -> BoxFuture<'_, Result<WithdrawalQuote>> {
213        dispatch!(
214            self,
215            AdapterCall::PrepareWithdrawal {
216                request: request.clone(),
217            },
218            AdapterReply::WithdrawalQuote,
219            "WithdrawalQuote"
220        )
221    }
222
223    fn withdraw(&self, request: &WithdrawRequest) -> BoxFuture<'_, Result<Withdrawal>> {
224        dispatch!(
225            self,
226            AdapterCall::Withdraw {
227                request: request.clone(),
228            },
229            AdapterReply::Withdrawal,
230            "Withdrawal"
231        )
232    }
233
234    fn deposit(&self, request: &TransferLookupRequest) -> BoxFuture<'_, Result<Deposit>> {
235        dispatch!(
236            self,
237            AdapterCall::Deposit {
238                request: request.clone(),
239            },
240            AdapterReply::Deposit,
241            "Deposit"
242        )
243    }
244
245    fn withdrawal(&self, request: &TransferLookupRequest) -> BoxFuture<'_, Result<Withdrawal>> {
246        dispatch!(
247            self,
248            AdapterCall::Withdrawal {
249                request: request.clone(),
250            },
251            AdapterReply::LookupWithdrawal,
252            "LookupWithdrawal"
253        )
254    }
255
256    fn cancel_withdrawal(&self, withdrawal_id: &str) -> BoxFuture<'_, Result<()>> {
257        let future = self.dispatcher.dispatch(AdapterCall::CancelWithdrawal {
258            withdrawal_id: withdrawal_id.to_owned(),
259        });
260        Box::pin(async move {
261            match future.await? {
262                AdapterReply::Unit => Ok(()),
263                reply => Err(unexpected_reply("Unit", &reply)),
264            }
265        })
266    }
267
268    fn deposits(&self, request: &TransferHistoryRequest) -> BoxFuture<'_, Result<Page<Deposit>>> {
269        dispatch!(
270            self,
271            AdapterCall::Deposits {
272                request: request.clone(),
273            },
274            AdapterReply::Deposits,
275            "Deposits"
276        )
277    }
278
279    fn withdrawals(
280        &self,
281        request: &TransferHistoryRequest,
282    ) -> BoxFuture<'_, Result<Page<Withdrawal>>> {
283        dispatch!(
284            self,
285            AdapterCall::Withdrawals {
286                request: request.clone(),
287            },
288            AdapterReply::Withdrawals,
289            "Withdrawals"
290        )
291    }
292
293    fn open_orders(&self, market: Option<&Market>) -> BoxFuture<'_, Result<Vec<Order>>> {
294        dispatch!(
295            self,
296            AdapterCall::OpenOrders {
297                market: market.cloned(),
298            },
299            AdapterReply::OpenOrders,
300            "OpenOrders"
301        )
302    }
303
304    fn order(&self, market: &Market, order_id: &str) -> BoxFuture<'_, Result<Order>> {
305        dispatch!(
306            self,
307            AdapterCall::Order {
308                market: market.clone(),
309                order_id: order_id.to_owned(),
310            },
311            AdapterReply::Order,
312            "Order"
313        )
314    }
315
316    fn order_by_client_id(&self, market: &Market, client_id: &str) -> BoxFuture<'_, Result<Order>> {
317        dispatch!(
318            self,
319            AdapterCall::OrderByClientId {
320                market: market.clone(),
321                client_id: client_id.to_owned(),
322            },
323            AdapterReply::Order,
324            "Order"
325        )
326    }
327
328    fn orders_by_ids(&self, request: &OrderLookupRequest) -> BoxFuture<'_, Result<Vec<Order>>> {
329        dispatch!(
330            self,
331            AdapterCall::OrdersByIds {
332                request: request.clone(),
333            },
334            AdapterReply::OrdersByIds,
335            "OrdersByIds"
336        )
337    }
338
339    fn order_history(&self, request: &OrderHistoryRequest) -> BoxFuture<'_, Result<Page<Order>>> {
340        dispatch!(
341            self,
342            AdapterCall::OrderHistory {
343                request: request.clone(),
344            },
345            AdapterReply::OrderHistory,
346            "OrderHistory"
347        )
348    }
349
350    fn subscribe_account(&self, config: &StreamConfig) -> BoxFuture<'_, Result<AccountStream>> {
351        dispatch!(
352            self,
353            AdapterCall::SubscribeAccount {
354                config: config.clone(),
355            },
356            AdapterReply::AccountStream,
357            "AccountStream"
358        )
359    }
360
361    fn place_order(&self, request: &OrderRequest) -> BoxFuture<'_, Result<Order>> {
362        dispatch!(
363            self,
364            AdapterCall::PlaceOrder {
365                request: request.clone(),
366            },
367            AdapterReply::PlaceOrder,
368            "PlaceOrder"
369        )
370    }
371
372    fn cancel_order(&self, market: &Market, order_id: &str) -> BoxFuture<'_, Result<()>> {
373        let future = self.dispatcher.dispatch(AdapterCall::CancelOrder {
374            market: market.clone(),
375            order_id: order_id.to_owned(),
376        });
377        Box::pin(async move {
378            match future.await? {
379                AdapterReply::Unit => Ok(()),
380                reply => Err(unexpected_reply("Unit", &reply)),
381            }
382        })
383    }
384
385    fn cancel_order_by_client_id(
386        &self,
387        market: &Market,
388        client_id: &str,
389    ) -> BoxFuture<'_, Result<()>> {
390        let future = self
391            .dispatcher
392            .dispatch(AdapterCall::CancelOrderByClientId {
393                market: market.clone(),
394                client_id: client_id.to_owned(),
395            });
396        Box::pin(async move {
397            match future.await? {
398                AdapterReply::Unit => Ok(()),
399                reply => Err(unexpected_reply("Unit", &reply)),
400            }
401        })
402    }
403
404    fn cancel_orders(
405        &self,
406        request: &CancelOrdersRequest,
407    ) -> BoxFuture<'_, Result<CancelOrdersResult>> {
408        dispatch!(
409            self,
410            AdapterCall::CancelOrders {
411                request: request.clone(),
412            },
413            AdapterReply::CancelOrdersResult,
414            "CancelOrdersResult"
415        )
416    }
417
418    fn positions(&self, market: Option<&Market>) -> BoxFuture<'_, Result<Vec<Position>>> {
419        dispatch!(
420            self,
421            AdapterCall::Positions {
422                market: market.cloned(),
423            },
424            AdapterReply::Positions,
425            "Positions"
426        )
427    }
428
429    fn margin_summary(&self) -> BoxFuture<'_, Result<MarginSummary>> {
430        dispatch!(
431            self,
432            AdapterCall::MarginSummary,
433            AdapterReply::MarginSummary,
434            "MarginSummary"
435        )
436    }
437
438    fn funding_rates(&self, request: &HistoryRequest) -> BoxFuture<'_, Result<Page<FundingRate>>> {
439        dispatch!(
440            self,
441            AdapterCall::FundingRates {
442                request: request.clone(),
443            },
444            AdapterReply::FundingRates,
445            "FundingRates"
446        )
447    }
448
449    fn funding_payments(
450        &self,
451        request: &HistoryRequest,
452    ) -> BoxFuture<'_, Result<Page<FundingPayment>>> {
453        dispatch!(
454            self,
455            AdapterCall::FundingPayments {
456                request: request.clone(),
457            },
458            AdapterReply::FundingPayments,
459            "FundingPayments"
460        )
461    }
462
463    fn set_margin(&self, request: &MarginRequest) -> BoxFuture<'_, Result<()>> {
464        let future = self.dispatcher.dispatch(AdapterCall::SetMargin {
465            request: request.clone(),
466        });
467        Box::pin(async move {
468            match future.await? {
469                AdapterReply::Unit => Ok(()),
470                reply => Err(unexpected_reply("Unit", &reply)),
471            }
472        })
473    }
474}
475
476fn unexpected_reply(expected: &str, reply: &AdapterReply) -> maxt::Error {
477    maxt::Error::adapter(format!(
478        "foreign dispatcher returned {} where {expected} was required",
479        reply.kind()
480    ))
481}