nautilus_model/python/defi/
types.rs1use std::{
19 collections::hash_map::DefaultHasher,
20 hash::{Hash, Hasher},
21 str::FromStr,
22 sync::Arc,
23};
24
25use nautilus_core::python::{IntoPyObjectNautilusExt, to_pyvalue_err};
26use pyo3::{basic::CompareOp, prelude::*};
27
28use crate::{
29 defi::{
30 AmmType, Blockchain, Chain, Dex, DexType, Pool, Token, chain::chains,
31 validation::validate_address,
32 },
33 identifiers::InstrumentId,
34};
35
36#[pymethods]
37#[pyo3_stub_gen::derive::gen_stub_pymethods]
38impl Chain {
39 #[new]
41 fn py_new(name: Blockchain, chain_id: u32) -> Self {
42 Self::new(name, chain_id)
43 }
44
45 fn __str__(&self) -> String {
46 self.to_string()
47 }
48
49 fn __repr__(&self) -> String {
50 format!("{self:?}")
51 }
52
53 fn __hash__(&self) -> u64 {
54 let mut hasher = DefaultHasher::new();
55 self.chain_id.hash(&mut hasher);
56 hasher.finish()
57 }
58
59 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
60 match op {
61 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
62 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
63 _ => py.NotImplemented(),
64 }
65 }
66
67 #[getter]
68 #[pyo3(name = "name")]
69 fn py_name(&self) -> Blockchain {
70 self.name
71 }
72
73 #[getter]
74 #[pyo3(name = "chain_id")]
75 fn py_chain_id(&self) -> u32 {
76 self.chain_id
77 }
78
79 #[getter]
80 #[pyo3(name = "hypersync_url")]
81 fn py_hypersync_url(&self) -> &str {
82 &self.hypersync_url
83 }
84
85 #[getter]
86 #[pyo3(name = "rpc_url")]
87 fn py_rpc_url(&self) -> Option<&str> {
88 self.rpc_url.as_deref()
89 }
90
91 #[getter]
92 #[pyo3(name = "native_currency_decimals")]
93 fn py_native_currency_decimals(&self) -> u8 {
94 self.native_currency_decimals
95 }
96
97 #[pyo3(name = "set_rpc_url")]
99 fn py_set_rpc_url(&mut self, rpc_url: String) {
100 self.set_rpc_url(rpc_url);
101 }
102
103 #[staticmethod]
107 #[pyo3(name = "from_chain_name")]
108 fn py_from_chain_name(chain_name: &str) -> PyResult<Self> {
109 Self::from_chain_name(chain_name)
110 .cloned()
111 .ok_or_else(|| to_pyvalue_err(format!("`chain_name` '{chain_name}' is not recognized")))
112 }
113
114 #[staticmethod]
116 #[pyo3(name = "from_chain_id")]
117 fn py_from_chain_id(chain_id: u32) -> Option<Self> {
118 Self::from_chain_id(chain_id).cloned()
119 }
120
121 #[staticmethod]
122 #[pyo3(name = "ARBITRUM")]
123 fn py_arbitrum_chain() -> Self {
124 chains::ARBITRUM.clone()
125 }
126}
127
128#[pymethods]
129#[pyo3_stub_gen::derive::gen_stub_pymethods]
130impl Token {
131 #[new]
133 #[expect(clippy::needless_pass_by_value)]
134 fn py_new(
135 chain: Chain,
136 address: String,
137 name: String,
138 symbol: String,
139 decimals: u8,
140 ) -> PyResult<Self> {
141 let address = address.parse().map_err(to_pyvalue_err)?;
142 Ok(Self::new(Arc::new(chain), address, name, symbol, decimals))
143 }
144
145 fn __str__(&self) -> String {
146 self.to_string()
147 }
148
149 fn __repr__(&self) -> String {
150 format!("{self:?}")
151 }
152
153 fn __hash__(&self) -> u64 {
154 let mut hasher = DefaultHasher::new();
155 self.chain.chain_id.hash(&mut hasher);
156 self.address.hash(&mut hasher);
157 hasher.finish()
158 }
159
160 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
161 match op {
162 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
163 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
164 _ => py.NotImplemented(),
165 }
166 }
167
168 #[getter]
169 #[pyo3(name = "chain")]
170 fn py_chain(&self) -> Chain {
171 self.chain.as_ref().clone()
172 }
173
174 #[getter]
175 #[pyo3(name = "address")]
176 fn py_address(&self) -> String {
177 self.address.to_string()
178 }
179
180 #[getter]
181 #[pyo3(name = "name")]
182 fn py_name(&self) -> &str {
183 &self.name
184 }
185
186 #[getter]
187 #[pyo3(name = "symbol")]
188 fn py_symbol(&self) -> &str {
189 &self.symbol
190 }
191
192 #[getter]
193 #[pyo3(name = "decimals")]
194 fn py_decimals(&self) -> u8 {
195 self.decimals
196 }
197}
198
199#[pymethods]
200#[pyo3_stub_gen::derive::gen_stub_pymethods]
201impl Dex {
202 #[new]
204 #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
205 fn py_new(
206 chain: Chain,
207 name: String,
208 factory: String,
209 factory_creation_block: u64,
210 amm_type: String,
211 pool_created_event: &str,
212 swap_event: &str,
213 mint_event: &str,
214 burn_event: &str,
215 collect_event: &str,
216 ) -> PyResult<Self> {
217 let amm_type = AmmType::from_str(&amm_type).map_err(to_pyvalue_err)?;
218 let dex_type = DexType::from_dex_name(&name)
219 .ok_or_else(|| to_pyvalue_err(format!("Invalid DEX name: {name}")))?;
220
221 validate_address(&factory).map_err(to_pyvalue_err)?;
222
223 Ok(Self::new(
224 chain,
225 dex_type,
226 &factory,
227 factory_creation_block,
228 amm_type,
229 pool_created_event,
230 swap_event,
231 mint_event,
232 burn_event,
233 collect_event,
234 ))
235 }
236
237 fn __str__(&self) -> String {
238 self.to_string()
239 }
240
241 fn __repr__(&self) -> String {
242 format!("{self:?}")
243 }
244
245 fn __hash__(&self) -> u64 {
246 let mut hasher = DefaultHasher::new();
247 self.chain.chain_id.hash(&mut hasher);
248 self.name.hash(&mut hasher);
249 self.factory.hash(&mut hasher);
250 hasher.finish()
251 }
252
253 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
254 match op {
255 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
256 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
257 _ => py.NotImplemented(),
258 }
259 }
260
261 #[getter]
262 #[pyo3(name = "chain")]
263 fn py_chain(&self) -> Chain {
264 self.chain.clone()
265 }
266
267 #[getter]
268 #[pyo3(name = "name")]
269 fn py_name(&self) -> DexType {
270 self.name
271 }
272
273 #[getter]
274 #[pyo3(name = "factory")]
275 fn py_factory(&self) -> String {
276 self.factory.to_string()
277 }
278
279 #[getter]
280 #[pyo3(name = "factory_creation_block")]
281 fn py_factory_creation_block(&self) -> u64 {
282 self.factory_creation_block
283 }
284
285 #[getter]
286 #[pyo3(name = "pool_created_event")]
287 fn py_pool_created_event(&self) -> &str {
288 &self.pool_created_event
289 }
290
291 #[getter]
292 #[pyo3(name = "swap_created_event")]
293 fn py_swap_created_event(&self) -> &str {
294 &self.swap_created_event
295 }
296
297 #[getter]
298 #[pyo3(name = "mint_created_event")]
299 fn py_mint_created_event(&self) -> &str {
300 &self.mint_created_event
301 }
302
303 #[getter]
304 #[pyo3(name = "burn_created_event")]
305 fn py_burn_created_event(&self) -> &str {
306 &self.burn_created_event
307 }
308
309 #[getter]
310 #[pyo3(name = "amm_type")]
311 fn py_amm_type(&self) -> AmmType {
312 self.amm_type
313 }
314}
315
316#[pymethods]
317#[pyo3_stub_gen::derive::gen_stub_pymethods]
318impl Pool {
319 #[new]
344 #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
345 fn py_new(
346 chain: Chain,
347 dex: Dex,
348 address: String,
349 pool_identifier: String,
350 creation_block: u64,
351 token0: Token,
352 token1: Token,
353 fee: Option<u32>,
354 tick_spacing: Option<u32>,
355 ts_init: u64,
356 ) -> PyResult<Self> {
357 let address = address.parse().map_err(to_pyvalue_err)?;
358 let pool_identifier = pool_identifier.parse().map_err(to_pyvalue_err)?;
359 Ok(Self::new(
360 Arc::new(chain),
361 Arc::new(dex),
362 address,
363 pool_identifier,
364 creation_block,
365 token0,
366 token1,
367 fee,
368 tick_spacing,
369 ts_init.into(),
370 ))
371 }
372
373 fn __str__(&self) -> String {
374 self.to_string()
375 }
376
377 fn __repr__(&self) -> String {
378 format!("{self:?}")
379 }
380
381 fn __hash__(&self) -> u64 {
382 let mut hasher = DefaultHasher::new();
383 self.chain.chain_id.hash(&mut hasher);
384 self.address.hash(&mut hasher);
385 hasher.finish()
386 }
387
388 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
389 match op {
390 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
391 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
392 _ => py.NotImplemented(),
393 }
394 }
395
396 #[getter]
397 #[pyo3(name = "chain")]
398 fn py_chain(&self) -> Chain {
399 self.chain.as_ref().clone()
400 }
401
402 #[getter]
403 #[pyo3(name = "dex")]
404 fn py_dex(&self) -> Dex {
405 self.dex.as_ref().clone()
406 }
407
408 #[getter]
409 #[pyo3(name = "instrument_id")]
410 fn py_instrument_id(&self) -> InstrumentId {
411 self.instrument_id
412 }
413
414 #[getter]
415 #[pyo3(name = "address")]
416 fn py_address(&self) -> String {
417 self.address.to_string()
418 }
419
420 #[getter]
421 #[pyo3(name = "creation_block")]
422 fn py_creation_block(&self) -> u64 {
423 self.creation_block
424 }
425
426 #[getter]
427 #[pyo3(name = "token0")]
428 fn py_token0(&self) -> Token {
429 self.token0.clone()
430 }
431
432 #[getter]
433 #[pyo3(name = "token1")]
434 fn py_token1(&self) -> Token {
435 self.token1.clone()
436 }
437
438 #[getter]
439 #[pyo3(name = "fee")]
440 fn py_fee(&self) -> Option<u32> {
441 self.fee
442 }
443
444 #[getter]
445 #[pyo3(name = "tick_spacing")]
446 fn py_tick_spacing(&self) -> Option<u32> {
447 self.tick_spacing
448 }
449
450 #[getter]
451 #[pyo3(name = "ts_event")]
452 fn py_ts_event(&self) -> u64 {
453 self.ts_event.as_u64()
454 }
455
456 #[getter]
457 #[pyo3(name = "ts_init")]
458 fn py_ts_init(&self) -> u64 {
459 self.ts_init.as_u64()
460 }
461}