nautilus_lighter/python/
mod.rs1#![expect(
23 clippy::missing_errors_doc,
24 reason = "errors documented on underlying Rust methods"
25)]
26
27pub mod config;
28pub mod factories;
29
30use std::time::{SystemTime, UNIX_EPOCH};
31
32use nautilus_common::factories::{ClientConfig, DataClientFactory, ExecutionClientFactory};
33use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err};
34use nautilus_system::get_global_pyo3_registry;
35use pyo3::prelude::*;
36
37use crate::{
38 common::{
39 consts::{LIGHTER, LIGHTER_NAUTILUS_INTEGRATOR_ACCOUNT_INDEX},
40 credential::Credential,
41 enums::LighterEnvironment,
42 urls::lighter_chain_id,
43 },
44 config::{LighterDataClientConfig, LighterExecClientConfig},
45 factories::{LighterDataClientFactory, LighterExecutionClientFactory},
46 http::{
47 client::{LighterHttpClient, LighterRawHttpClient},
48 models::LighterSendTxRequest,
49 },
50 signing::{
51 auth_token::fresh_k,
52 tx::{ApproveIntegratorTxInfo, LighterTx, TxContext, TxInfoJson, sign_tx},
53 },
54};
55
56const TX_EXPIRY_MS: i64 = 5 * 60 * 1_000;
57
58#[expect(clippy::needless_pass_by_value)]
59fn extract_lighter_data_factory(
60 py: Python<'_>,
61 factory: Py<PyAny>,
62) -> PyResult<Box<dyn DataClientFactory>> {
63 match factory.extract::<LighterDataClientFactory>(py) {
64 Ok(f) => Ok(Box::new(f)),
65 Err(e) => Err(to_pyvalue_err(format!(
66 "Failed to extract LighterDataClientFactory: {e}"
67 ))),
68 }
69}
70
71#[expect(clippy::needless_pass_by_value)]
72fn extract_lighter_exec_factory(
73 py: Python<'_>,
74 factory: Py<PyAny>,
75) -> PyResult<Box<dyn ExecutionClientFactory>> {
76 match factory.extract::<LighterExecutionClientFactory>(py) {
77 Ok(f) => Ok(Box::new(f)),
78 Err(e) => Err(to_pyvalue_err(format!(
79 "Failed to extract LighterExecutionClientFactory: {e}"
80 ))),
81 }
82}
83
84#[expect(clippy::needless_pass_by_value)]
85fn extract_lighter_data_config(
86 py: Python<'_>,
87 config: Py<PyAny>,
88) -> PyResult<Box<dyn ClientConfig>> {
89 match config.extract::<LighterDataClientConfig>(py) {
90 Ok(c) => Ok(Box::new(c)),
91 Err(e) => Err(to_pyvalue_err(format!(
92 "Failed to extract LighterDataClientConfig: {e}"
93 ))),
94 }
95}
96
97#[expect(clippy::needless_pass_by_value)]
98fn extract_lighter_exec_config(
99 py: Python<'_>,
100 config: Py<PyAny>,
101) -> PyResult<Box<dyn ClientConfig>> {
102 match config.extract::<LighterExecClientConfig>(py) {
103 Ok(c) => Ok(Box::new(c)),
104 Err(e) => Err(to_pyvalue_err(format!(
105 "Failed to extract LighterExecClientConfig: {e}"
106 ))),
107 }
108}
109
110async fn submit_integrator_revocation(environment: LighterEnvironment) -> anyhow::Result<String> {
111 let credential = Credential::resolve(None, None, None, environment)?
112 .ok_or_else(|| anyhow::anyhow!("no Lighter L2 credentials in env"))?;
113 let chain_id = lighter_chain_id(environment);
114
115 let raw = LighterRawHttpClient::new(environment, None, 30, None)?;
116 let http = LighterHttpClient::from_raw(raw);
117 let next_nonce = http
118 .get_next_nonce(credential.account_index(), credential.api_key_index())
119 .await?
120 .nonce;
121
122 let now_ms = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis() as i64;
123 let tx = ApproveIntegratorTxInfo {
124 context: TxContext {
125 account_index: credential.account_index(),
126 api_key_index: credential.api_key_index(),
127 nonce: next_nonce,
128 expired_at: now_ms.saturating_add(TX_EXPIRY_MS),
129 },
130 integrator_account_index: LIGHTER_NAUTILUS_INTEGRATOR_ACCOUNT_INDEX as i64,
131 max_perps_taker_fee: 0,
132 max_perps_maker_fee: 0,
133 max_spot_taker_fee: 0,
134 max_spot_maker_fee: 0,
135 approval_expiry: 0,
136 skip_nonce: 0,
137 };
138
139 let l2_signed = sign_tx(&tx, chain_id, &credential.private_key()?, fresh_k());
140 let tx_info_str = TxInfoJson::approve_integrator(&tx, &l2_signed, "");
141 let request = LighterSendTxRequest::new(tx.tx_type() as u8, tx_info_str);
142 let response = http.send_tx(&request).await?;
143
144 Ok(format!(
145 "integrator={LIGHTER_NAUTILUS_INTEGRATOR_ACCOUNT_INDEX} account_index={} tx_hash={}",
146 credential.account_index(),
147 response.tx_hash,
148 ))
149}
150
151#[pyfunction]
165#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.adapters.lighter")]
166#[pyo3(name = "revoke_lighter_integrator", signature = (environment = LighterEnvironment::Mainnet))]
167fn py_revoke_lighter_integrator(
168 py: Python<'_>,
169 environment: LighterEnvironment,
170) -> PyResult<Bound<'_, PyAny>> {
171 pyo3_async_runtimes::tokio::future_into_py(py, async move {
172 submit_integrator_revocation(environment)
173 .await
174 .map(|s| format!("submitted revocation for {s}"))
175 .map_err(to_pyvalue_err)
176 })
177}
178
179#[pymodule]
181pub fn lighter(m: &Bound<'_, PyModule>) -> PyResult<()> {
182 m.add(stringify!(LIGHTER), LIGHTER)?;
183 m.add_class::<LighterEnvironment>()?;
184 m.add_class::<LighterDataClientConfig>()?;
185 m.add_class::<LighterExecClientConfig>()?;
186 m.add_class::<LighterDataClientFactory>()?;
187 m.add_class::<LighterExecutionClientFactory>()?;
188 m.add_function(wrap_pyfunction!(py_revoke_lighter_integrator, m)?)?;
189
190 let registry = get_global_pyo3_registry();
191
192 if let Err(e) =
193 registry.register_factory_extractor(LIGHTER.to_string(), extract_lighter_data_factory)
194 {
195 return Err(to_pyruntime_err(format!(
196 "Failed to register Lighter data factory extractor: {e}"
197 )));
198 }
199
200 if let Err(e) =
201 registry.register_exec_factory_extractor(LIGHTER.to_string(), extract_lighter_exec_factory)
202 {
203 return Err(to_pyruntime_err(format!(
204 "Failed to register Lighter exec factory extractor: {e}"
205 )));
206 }
207
208 if let Err(e) = registry.register_config_extractor(
209 "LighterDataClientConfig".to_string(),
210 extract_lighter_data_config,
211 ) {
212 return Err(to_pyruntime_err(format!(
213 "Failed to register Lighter data config extractor: {e}"
214 )));
215 }
216
217 if let Err(e) = registry.register_config_extractor(
218 "LighterExecClientConfig".to_string(),
219 extract_lighter_exec_config,
220 ) {
221 return Err(to_pyruntime_err(format!(
222 "Failed to register Lighter exec config extractor: {e}"
223 )));
224 }
225
226 Ok(())
227}