1use std::collections::HashMap;
17
18use nautilus_core::python::{IntoPyObjectNautilusExt, to_pyvalue_err};
19use nautilus_model::{
20 data::BarType,
21 enums::{OrderSide, OrderType, TimeInForce},
22 identifiers::{AccountId, ClientOrderId, InstrumentId, VenueOrderId},
23 instruments::Instrument,
24 orders::OrderAny,
25 python::{
26 instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
27 orders::pyobject_to_order_any,
28 },
29 types::{Price, Quantity},
30};
31use pyo3::{IntoPyObjectExt, prelude::*, types::PyList};
32use rust_decimal::Decimal;
33use serde_json::to_string;
34
35use crate::{
36 common::enums::HyperliquidEnvironment,
37 http::{client::HyperliquidHttpClient, parse::HyperliquidMarketType},
38};
39
40#[pymethods]
41#[pyo3_stub_gen::derive::gen_stub_pymethods]
42impl HyperliquidHttpClient {
43 #[new]
49 #[pyo3(signature = (private_key=None, vault_address=None, account_address=None, environment=HyperliquidEnvironment::Mainnet, timeout_secs=60, proxy_url=None, normalize_prices=true, include_builder_attribution=true))]
50 #[expect(clippy::too_many_arguments)]
51 fn py_new(
52 private_key: Option<String>,
53 vault_address: Option<String>,
54 account_address: Option<&str>,
55 environment: HyperliquidEnvironment,
56 timeout_secs: u64,
57 proxy_url: Option<String>,
58 normalize_prices: bool,
59 include_builder_attribution: bool,
60 ) -> PyResult<Self> {
61 let mut client = Self::with_credentials(
62 private_key,
63 vault_address,
64 account_address,
65 environment,
66 timeout_secs,
67 proxy_url,
68 )
69 .map_err(to_pyvalue_err)?;
70 client.set_normalize_prices(normalize_prices);
71 client.set_include_builder_attribution(include_builder_attribution);
72 Ok(client)
73 }
74
75 #[staticmethod]
81 #[pyo3(name = "from_env", signature = (environment=HyperliquidEnvironment::Mainnet, include_builder_attribution=true))]
82 fn py_from_env(
83 environment: HyperliquidEnvironment,
84 include_builder_attribution: bool,
85 ) -> PyResult<Self> {
86 let mut client = Self::from_env(environment).map_err(to_pyvalue_err)?;
87 client.set_include_builder_attribution(include_builder_attribution);
88 Ok(client)
89 }
90
91 #[staticmethod]
97 #[pyo3(name = "from_credentials", signature = (private_key, vault_address=None, environment=HyperliquidEnvironment::Mainnet, timeout_secs=60, proxy_url=None, include_builder_attribution=true))]
98 fn py_from_credentials(
99 private_key: &str,
100 vault_address: Option<&str>,
101 environment: HyperliquidEnvironment,
102 timeout_secs: u64,
103 proxy_url: Option<String>,
104 include_builder_attribution: bool,
105 ) -> PyResult<Self> {
106 let mut client = Self::from_credentials(
107 private_key,
108 vault_address,
109 environment,
110 timeout_secs,
111 proxy_url,
112 )
113 .map_err(to_pyvalue_err)?;
114 client.set_include_builder_attribution(include_builder_attribution);
115 Ok(client)
116 }
117
118 #[pyo3(name = "cache_instrument")]
129 fn py_cache_instrument(&self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
130 self.cache_instrument(&pyobject_to_instrument_any(py, instrument)?);
131 Ok(())
132 }
133
134 #[pyo3(name = "set_account_id")]
138 fn py_set_account_id(&mut self, account_id: &str) {
139 let account_id = AccountId::from(account_id);
140 self.set_account_id(account_id);
141 }
142
143 #[pyo3(name = "get_user_address")]
149 fn py_get_user_address(&self) -> PyResult<String> {
150 self.get_user_address().map_err(to_pyvalue_err)
151 }
152
153 #[pyo3(name = "get_spot_fill_coin_mapping")]
161 fn py_get_spot_fill_coin_mapping(&self) -> HashMap<String, String> {
162 self.get_spot_fill_coin_mapping()
163 .into_iter()
164 .map(|(k, v)| (k.to_string(), v.to_string()))
165 .collect()
166 }
167
168 #[pyo3(name = "get_spot_meta")]
170 fn py_get_spot_meta<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
171 let client = self.clone();
172 pyo3_async_runtimes::tokio::future_into_py(py, async move {
173 let meta = client.get_spot_meta().await.map_err(to_pyvalue_err)?;
174 to_string(&meta).map_err(to_pyvalue_err)
175 })
176 }
177
178 #[pyo3(name = "get_perp_meta")]
179 fn py_get_perp_meta<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
180 let client = self.clone();
181 pyo3_async_runtimes::tokio::future_into_py(py, async move {
182 let meta = client.load_perp_meta().await.map_err(to_pyvalue_err)?;
183 to_string(&meta).map_err(to_pyvalue_err)
184 })
185 }
186
187 #[pyo3(name = "build_all_dex_asset_ctxs_instrument_ids")]
192 fn py_build_all_dex_asset_ctxs_instrument_ids<'py>(
193 &self,
194 py: Python<'py>,
195 ) -> PyResult<Bound<'py, PyAny>> {
196 let client = self.clone();
197 pyo3_async_runtimes::tokio::future_into_py(py, async move {
198 let mapping = client
199 .build_all_dex_asset_ctxs_instrument_ids()
200 .await
201 .map_err(to_pyvalue_err)?;
202 Ok(mapping.into_iter().collect::<HashMap<_, _>>())
203 })
204 }
205
206 #[pyo3(name = "load_instrument_definitions", signature = (include_spot=true, include_perps=true, include_perps_hip3=false, include_outcomes=false))]
207 fn py_load_instrument_definitions<'py>(
208 &self,
209 py: Python<'py>,
210 include_spot: bool,
211 include_perps: bool,
212 include_perps_hip3: bool,
213 include_outcomes: bool,
214 ) -> PyResult<Bound<'py, PyAny>> {
215 let client = self.clone();
216
217 pyo3_async_runtimes::tokio::future_into_py(py, async move {
218 let mut defs = client
219 .request_instrument_defs()
220 .await
221 .map_err(to_pyvalue_err)?;
222
223 defs.retain(|def| match def.market_type {
224 HyperliquidMarketType::Perp => {
225 if def.is_hip3 {
226 include_perps_hip3
227 } else {
228 include_perps
229 }
230 }
231 HyperliquidMarketType::Spot => include_spot,
232 HyperliquidMarketType::Outcome => include_outcomes,
233 });
234
235 let mut instruments = client.convert_defs(defs);
236 instruments.sort_by_key(|instrument| instrument.id());
237
238 Python::attach(|py| {
239 let mut py_instruments = Vec::with_capacity(instruments.len());
240 for instrument in instruments {
241 py_instruments.push(instrument_any_to_pyobject(py, instrument)?);
242 }
243
244 let py_list = PyList::new(py, &py_instruments)?;
245 Ok(py_list.into_any().unbind())
246 })
247 })
248 }
249
250 #[pyo3(name = "request_quote_ticks", signature = (instrument_id, start=None, end=None, limit=None))]
251 fn py_request_quote_ticks<'py>(
252 &self,
253 py: Python<'py>,
254 instrument_id: InstrumentId,
255 start: Option<jiff::Timestamp>,
256 end: Option<jiff::Timestamp>,
257 limit: Option<u32>,
258 ) -> PyResult<Bound<'py, PyAny>> {
259 let _ = (instrument_id, start, end, limit);
260 pyo3_async_runtimes::tokio::future_into_py(py, async move {
261 Err::<Vec<u8>, _>(to_pyvalue_err(anyhow::anyhow!(
262 "Hyperliquid does not provide historical quotes via HTTP API"
263 )))
264 })
265 }
266
267 #[pyo3(name = "request_trade_ticks", signature = (instrument_id, start=None, end=None, limit=None))]
268 fn py_request_trade_ticks<'py>(
269 &self,
270 py: Python<'py>,
271 instrument_id: InstrumentId,
272 start: Option<jiff::Timestamp>,
273 end: Option<jiff::Timestamp>,
274 limit: Option<u32>,
275 ) -> PyResult<Bound<'py, PyAny>> {
276 let _ = (instrument_id, start, end, limit);
277 pyo3_async_runtimes::tokio::future_into_py(py, async move {
278 Err::<Vec<u8>, _>(to_pyvalue_err(anyhow::anyhow!(
279 "Hyperliquid does not provide historical market trades via HTTP API"
280 )))
281 })
282 }
283
284 #[pyo3(name = "request_public_trades", signature = (instrument_id, start=None, end=None, limit=None))]
293 #[gen_stub(override_return_type(type_repr = "typing.Any", imports = ("typing",)))]
294 fn py_request_public_trades<'py>(
295 &self,
296 py: Python<'py>,
297 instrument_id: InstrumentId,
298 start: Option<jiff::Timestamp>,
299 end: Option<jiff::Timestamp>,
300 limit: Option<u32>,
301 ) -> PyResult<Bound<'py, PyAny>> {
302 let client = self.clone();
303
304 pyo3_async_runtimes::tokio::future_into_py(py, async move {
305 let trades = client
306 .request_public_trades(instrument_id, start, end, limit.map(|limit| limit as usize))
307 .await
308 .map_err(to_pyvalue_err)?;
309
310 Python::attach(|py| {
311 let py_trades = trades
312 .into_iter()
313 .map(|trade| trade.into_py_any(py))
314 .collect::<PyResult<Vec<_>>>()?;
315 let pylist = PyList::new(py, py_trades)?;
316 Ok(pylist.into_py_any_unwrap(py))
317 })
318 })
319 }
320
321 #[pyo3(name = "request_bars", signature = (bar_type, start=None, end=None, limit=None))]
338 fn py_request_bars<'py>(
339 &self,
340 py: Python<'py>,
341 bar_type: BarType,
342 start: Option<jiff::Timestamp>,
343 end: Option<jiff::Timestamp>,
344 limit: Option<u32>,
345 ) -> PyResult<Bound<'py, PyAny>> {
346 let client = self.clone();
347
348 pyo3_async_runtimes::tokio::future_into_py(py, async move {
349 let bars = client
350 .request_bars(bar_type, start, end, limit)
351 .await
352 .map_err(to_pyvalue_err)?;
353
354 Python::attach(|py| {
355 let py_bars = bars
356 .into_iter()
357 .map(|bar| bar.into_py_any(py))
358 .collect::<PyResult<Vec<_>>>()?;
359 let pylist = PyList::new(py, py_bars)?;
360 Ok(pylist.into_py_any_unwrap(py))
361 })
362 })
363 }
364
365 #[pyo3(name = "submit_order", signature = (
372 instrument_id,
373 client_order_id,
374 order_side,
375 order_type,
376 quantity,
377 time_in_force,
378 price=None,
379 trigger_price=None,
380 post_only=false,
381 reduce_only=false,
382 ))]
383 #[expect(clippy::too_many_arguments)]
384 fn py_submit_order<'py>(
385 &self,
386 py: Python<'py>,
387 instrument_id: InstrumentId,
388 client_order_id: ClientOrderId,
389 order_side: OrderSide,
390 order_type: OrderType,
391 quantity: Quantity,
392 time_in_force: TimeInForce,
393 price: Option<Price>,
394 trigger_price: Option<Price>,
395 post_only: bool,
396 reduce_only: bool,
397 ) -> PyResult<Bound<'py, PyAny>> {
398 let client = self.clone();
399
400 pyo3_async_runtimes::tokio::future_into_py(py, async move {
401 let report = client
402 .submit_order(
403 instrument_id,
404 client_order_id,
405 order_side,
406 order_type,
407 quantity,
408 time_in_force,
409 price,
410 trigger_price,
411 post_only,
412 reduce_only,
413 )
414 .await
415 .map_err(to_pyvalue_err)?;
416
417 Python::attach(|py| report.into_py_any(py))
418 })
419 }
420
421 #[pyo3(name = "cancel_order", signature = (
431 instrument_id,
432 client_order_id=None,
433 venue_order_id=None,
434 ))]
435 fn py_cancel_order<'py>(
436 &self,
437 py: Python<'py>,
438 instrument_id: InstrumentId,
439 client_order_id: Option<ClientOrderId>,
440 venue_order_id: Option<VenueOrderId>,
441 ) -> PyResult<Bound<'py, PyAny>> {
442 let client = self.clone();
443
444 pyo3_async_runtimes::tokio::future_into_py(py, async move {
445 client
446 .cancel_order(instrument_id, client_order_id, venue_order_id)
447 .await
448 .map_err(to_pyvalue_err)?;
449 Ok(())
450 })
451 }
452
453 #[pyo3(name = "modify_order")]
463 #[expect(clippy::too_many_arguments)]
464 fn py_modify_order<'py>(
465 &self,
466 py: Python<'py>,
467 instrument_id: InstrumentId,
468 venue_order_id: Option<VenueOrderId>,
469 order_side: OrderSide,
470 order_type: OrderType,
471 price: Price,
472 quantity: Quantity,
473 trigger_price: Option<Price>,
474 reduce_only: bool,
475 post_only: bool,
476 time_in_force: TimeInForce,
477 client_order_id: Option<ClientOrderId>,
478 ) -> PyResult<Bound<'py, PyAny>> {
479 let client = self.clone();
480
481 pyo3_async_runtimes::tokio::future_into_py(py, async move {
482 client
483 .modify_order(
484 instrument_id,
485 venue_order_id,
486 order_side,
487 order_type,
488 price,
489 quantity,
490 trigger_price,
491 reduce_only,
492 post_only,
493 time_in_force,
494 client_order_id,
495 )
496 .await
497 .map_err(to_pyvalue_err)?;
498 Ok(())
499 })
500 }
501
502 #[pyo3(name = "submit_orders")]
511 fn py_submit_orders<'py>(
512 &self,
513 py: Python<'py>,
514 orders: Vec<Py<PyAny>>,
515 ) -> PyResult<Bound<'py, PyAny>> {
516 let client = self.clone();
517
518 pyo3_async_runtimes::tokio::future_into_py(py, async move {
519 let order_anys: Vec<OrderAny> = Python::attach(|py| {
520 orders
521 .into_iter()
522 .map(|order| pyobject_to_order_any(py, order))
523 .collect::<PyResult<Vec<_>>>()
524 .map_err(to_pyvalue_err)
525 })?;
526
527 let order_refs: Vec<&OrderAny> = order_anys.iter().collect();
528
529 let reports = client
530 .submit_orders(&order_refs)
531 .await
532 .map_err(to_pyvalue_err)?;
533
534 Python::attach(|py| {
535 let py_reports = reports
536 .into_iter()
537 .map(|report| report.into_py_any(py))
538 .collect::<PyResult<Vec<_>>>()?;
539 let pylist = PyList::new(py, py_reports)?;
540 Ok(pylist.into_py_any_unwrap(py))
541 })
542 })
543 }
544
545 #[pyo3(name = "request_order_status_reports")]
560 fn py_request_order_status_reports<'py>(
561 &self,
562 py: Python<'py>,
563 instrument_id: Option<&str>,
564 ) -> PyResult<Bound<'py, PyAny>> {
565 let client = self.clone();
566 let instrument_id = instrument_id.map(InstrumentId::from);
567
568 pyo3_async_runtimes::tokio::future_into_py(py, async move {
569 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
570 let reports = client
571 .request_order_status_reports(&account_address, instrument_id)
572 .await
573 .map_err(to_pyvalue_err)?;
574
575 Python::attach(|py| {
576 let py_reports = reports
577 .into_iter()
578 .map(|report| report.into_py_any(py))
579 .collect::<PyResult<Vec<_>>>()?;
580 let pylist = PyList::new(py, py_reports)?;
581 Ok(pylist.into_py_any_unwrap(py))
582 })
583 })
584 }
585
586 #[pyo3(name = "request_order_status_report")]
598 #[pyo3(signature = (venue_order_id=None, client_order_id=None))]
599 fn py_request_order_status_report<'py>(
600 &self,
601 py: Python<'py>,
602 venue_order_id: Option<&str>,
603 client_order_id: Option<&str>,
604 ) -> PyResult<Bound<'py, PyAny>> {
605 let client = self.clone();
606 let venue_order_id = venue_order_id.map(VenueOrderId::from);
607 let client_order_id = client_order_id.map(ClientOrderId::from);
608
609 pyo3_async_runtimes::tokio::future_into_py(py, async move {
610 if venue_order_id.is_none() && client_order_id.is_none() {
611 return Err(to_pyvalue_err(
612 "at least one of venue_order_id or client_order_id is required",
613 ));
614 }
615
616 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
617
618 if let Some(coid) = client_order_id.as_ref()
619 && let Some(report) = client
620 .request_order_status_report_by_client_order_id(&account_address, coid)
621 .await
622 .map_err(to_pyvalue_err)?
623 {
624 return Python::attach(|py| report.into_py_any(py));
625 }
626
627 let report = if let Some(vid) = venue_order_id.as_ref() {
628 let oid: u64 = vid
629 .as_str()
630 .parse()
631 .map_err(|e| to_pyvalue_err(format!("invalid venue_order_id: {e}")))?;
632
633 client
634 .request_order_status_report(&account_address, oid)
635 .await
636 .map_err(to_pyvalue_err)?
637 } else {
638 None
639 };
640
641 Python::attach(|py| match report {
642 Some(report) => report.into_py_any(py),
643 None => Ok(py.None()),
644 })
645 })
646 }
647
648 #[pyo3(name = "request_fill_reports")]
664 fn py_request_fill_reports<'py>(
665 &self,
666 py: Python<'py>,
667 instrument_id: Option<&str>,
668 ) -> PyResult<Bound<'py, PyAny>> {
669 let client = self.clone();
670 let instrument_id = instrument_id.map(InstrumentId::from);
671
672 pyo3_async_runtimes::tokio::future_into_py(py, async move {
673 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
674 let reports = client
675 .request_fill_reports(&account_address, instrument_id)
676 .await
677 .map_err(to_pyvalue_err)?;
678
679 Python::attach(|py| {
680 let py_reports = reports
681 .into_iter()
682 .map(|report| report.into_py_any(py))
683 .collect::<PyResult<Vec<_>>>()?;
684 let pylist = PyList::new(py, py_reports)?;
685 Ok(pylist.into_py_any_unwrap(py))
686 })
687 })
688 }
689
690 #[pyo3(name = "request_position_status_reports")]
714 fn py_request_position_status_reports<'py>(
715 &self,
716 py: Python<'py>,
717 instrument_id: Option<&str>,
718 ) -> PyResult<Bound<'py, PyAny>> {
719 let client = self.clone();
720 let instrument_id = instrument_id.map(InstrumentId::from);
721
722 pyo3_async_runtimes::tokio::future_into_py(py, async move {
723 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
724 let reports = client
725 .request_position_status_reports(&account_address, instrument_id)
726 .await
727 .map_err(to_pyvalue_err)?;
728
729 Python::attach(|py| {
730 let py_reports = reports
731 .into_iter()
732 .map(|report| report.into_py_any(py))
733 .collect::<PyResult<Vec<_>>>()?;
734 let pylist = PyList::new(py, py_reports)?;
735 Ok(pylist.into_py_any_unwrap(py))
736 })
737 })
738 }
739
740 #[pyo3(name = "request_account_state")]
754 fn py_request_account_state<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
755 let client = self.clone();
756
757 pyo3_async_runtimes::tokio::future_into_py(py, async move {
758 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
759 let account_state = client
760 .request_account_state(&account_address)
761 .await
762 .map_err(to_pyvalue_err)?;
763
764 Python::attach(|py| account_state.into_py_any(py))
765 })
766 }
767
768 #[pyo3(name = "request_spot_balances")]
779 fn py_request_spot_balances<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
780 let client = self.clone();
781
782 pyo3_async_runtimes::tokio::future_into_py(py, async move {
783 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
784 let balances = client
785 .request_spot_balances(&account_address)
786 .await
787 .map_err(to_pyvalue_err)?;
788
789 Python::attach(|py| {
790 let py_balances = balances
791 .into_iter()
792 .map(|balance| balance.into_py_any(py))
793 .collect::<PyResult<Vec<_>>>()?;
794 let pylist = PyList::new(py, py_balances)?;
795 Ok(pylist.into_py_any_unwrap(py))
796 })
797 })
798 }
799
800 #[pyo3(name = "request_spot_position_status_reports")]
816 fn py_request_spot_position_status_reports<'py>(
817 &self,
818 py: Python<'py>,
819 instrument_id: Option<&str>,
820 ) -> PyResult<Bound<'py, PyAny>> {
821 let client = self.clone();
822 let instrument_id = instrument_id.map(InstrumentId::from);
823
824 pyo3_async_runtimes::tokio::future_into_py(py, async move {
825 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
826 let reports = client
827 .request_spot_position_status_reports(&account_address, instrument_id)
828 .await
829 .map_err(to_pyvalue_err)?;
830
831 Python::attach(|py| {
832 let py_reports = reports
833 .into_iter()
834 .map(|report| report.into_py_any(py))
835 .collect::<PyResult<Vec<_>>>()?;
836 let pylist = PyList::new(py, py_reports)?;
837 Ok(pylist.into_py_any_unwrap(py))
838 })
839 })
840 }
841
842 #[pyo3(name = "info_spot_clearinghouse_state")]
844 fn py_info_spot_clearinghouse_state<'py>(
845 &self,
846 py: Python<'py>,
847 ) -> PyResult<Bound<'py, PyAny>> {
848 let client = self.clone();
849
850 pyo3_async_runtimes::tokio::future_into_py(py, async move {
851 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
852 let json = client
853 .info_spot_clearinghouse_state(&account_address)
854 .await
855 .map_err(to_pyvalue_err)?;
856 to_string(&json).map_err(to_pyvalue_err)
857 })
858 }
859
860 #[pyo3(name = "info_user_fees")]
862 fn py_info_user_fees<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
863 let client = self.clone();
864
865 pyo3_async_runtimes::tokio::future_into_py(py, async move {
866 let account_address = client.get_account_address().map_err(to_pyvalue_err)?;
867 let json = client
868 .info_user_fees(&account_address)
869 .await
870 .map_err(to_pyvalue_err)?;
871 to_string(&json).map_err(to_pyvalue_err)
872 })
873 }
874
875 #[pyo3(name = "submit_split_outcome")]
889 fn py_submit_split_outcome<'py>(
890 &self,
891 py: Python<'py>,
892 outcome: u32,
893 amount: Decimal,
894 ) -> PyResult<Bound<'py, PyAny>> {
895 let client = self.clone();
896
897 pyo3_async_runtimes::tokio::future_into_py(py, async move {
898 let response = client
899 .submit_split_outcome(outcome, amount)
900 .await
901 .map_err(to_pyvalue_err)?;
902 to_string(&response).map_err(to_pyvalue_err)
903 })
904 }
905
906 #[pyo3(name = "submit_merge_outcome", signature = (outcome, amount=None))]
917 fn py_submit_merge_outcome<'py>(
918 &self,
919 py: Python<'py>,
920 outcome: u32,
921 amount: Option<Decimal>,
922 ) -> PyResult<Bound<'py, PyAny>> {
923 let client = self.clone();
924
925 pyo3_async_runtimes::tokio::future_into_py(py, async move {
926 let response = client
927 .submit_merge_outcome(outcome, amount)
928 .await
929 .map_err(to_pyvalue_err)?;
930 to_string(&response).map_err(to_pyvalue_err)
931 })
932 }
933
934 #[pyo3(name = "submit_merge_question", signature = (question, amount=None))]
944 fn py_submit_merge_question<'py>(
945 &self,
946 py: Python<'py>,
947 question: u32,
948 amount: Option<Decimal>,
949 ) -> PyResult<Bound<'py, PyAny>> {
950 let client = self.clone();
951
952 pyo3_async_runtimes::tokio::future_into_py(py, async move {
953 let response = client
954 .submit_merge_question(question, amount)
955 .await
956 .map_err(to_pyvalue_err)?;
957 to_string(&response).map_err(to_pyvalue_err)
958 })
959 }
960
961 #[pyo3(name = "submit_negate_outcome")]
971 fn py_submit_negate_outcome<'py>(
972 &self,
973 py: Python<'py>,
974 question: u32,
975 outcome: u32,
976 amount: Decimal,
977 ) -> PyResult<Bound<'py, PyAny>> {
978 let client = self.clone();
979
980 pyo3_async_runtimes::tokio::future_into_py(py, async move {
981 let response = client
982 .submit_negate_outcome(question, outcome, amount)
983 .await
984 .map_err(to_pyvalue_err)?;
985 to_string(&response).map_err(to_pyvalue_err)
986 })
987 }
988}