Skip to main content

nautilus_model/python/defi/
data.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Python bindings for DeFi data types.
17
18use std::{
19    collections::hash_map::DefaultHasher,
20    hash::{Hash, Hasher},
21    sync::Arc,
22};
23
24use nautilus_core::python::{IntoPyObjectNautilusExt, to_pyvalue_err};
25use pyo3::{basic::CompareOp, prelude::*};
26
27use crate::{
28    defi::{
29        Chain, Dex,
30        chain::Blockchain,
31        data::{
32            Block, DefiData, PoolFeeCollect, PoolFeeProtocolCollect, PoolFeeProtocolUpdate,
33            PoolFlash, PoolLiquidityUpdate, PoolLiquidityUpdateType, PoolSwap, Transaction,
34        },
35    },
36    identifiers::InstrumentId,
37};
38
39#[pymethods]
40#[pyo3_stub_gen::derive::gen_stub_pymethods]
41impl Block {
42    /// Represents an Ethereum-compatible blockchain block with essential metadata.
43    #[new]
44    #[expect(clippy::too_many_arguments)]
45    fn py_new(
46        chain: Blockchain,
47        hash: String,
48        parent_hash: String,
49        number: u64,
50        miner: String,
51        gas_limit: u64,
52        gas_used: u64,
53        timestamp: u64,
54    ) -> Self {
55        Self::new(
56            hash,
57            parent_hash,
58            number,
59            miner.into(),
60            gas_limit,
61            gas_used,
62            timestamp.into(),
63            Some(chain),
64        )
65    }
66
67    /// Returns the blockchain for this block.
68    #[getter]
69    #[pyo3(name = "chain")]
70    fn py_chain(&self) -> Option<Blockchain> {
71        self.chain
72    }
73
74    #[getter]
75    #[pyo3(name = "hash")]
76    fn py_hash(&self) -> &str {
77        &self.hash
78    }
79
80    #[getter]
81    #[pyo3(name = "number")]
82    fn py_number(&self) -> u64 {
83        self.number
84    }
85
86    #[getter]
87    #[pyo3(name = "parent_hash")]
88    fn py_parent_hash(&self) -> &str {
89        &self.parent_hash
90    }
91
92    #[getter]
93    #[pyo3(name = "miner")]
94    fn py_miner(&self) -> &str {
95        &self.miner
96    }
97
98    #[getter]
99    #[pyo3(name = "gas_limit")]
100    fn py_gas_limit(&self) -> u64 {
101        self.gas_limit
102    }
103
104    #[getter]
105    #[pyo3(name = "gas_used")]
106    fn py_gas_used(&self) -> u64 {
107        self.gas_used
108    }
109
110    #[getter]
111    #[pyo3(name = "base_fee_per_gas")]
112    fn py_base_fee_per_gas(&self) -> Option<String> {
113        self.base_fee_per_gas.map(|x| x.to_string())
114    }
115
116    #[getter]
117    #[pyo3(name = "blob_gas_used")]
118    fn py_blob_gas_used(&self) -> Option<String> {
119        self.blob_gas_used.map(|x| x.to_string())
120    }
121
122    #[getter]
123    #[pyo3(name = "excess_blob_gas")]
124    fn py_excess_blob_gas(&self) -> Option<String> {
125        self.excess_blob_gas.map(|x| x.to_string())
126    }
127
128    #[getter]
129    #[pyo3(name = "l1_gas_price")]
130    fn py_l1_gas_price(&self) -> Option<String> {
131        self.l1_gas_price.map(|x| x.to_string())
132    }
133
134    #[getter]
135    #[pyo3(name = "l1_gas_used")]
136    fn py_l1_gas_used(&self) -> Option<u64> {
137        self.l1_gas_used
138    }
139
140    #[getter]
141    #[pyo3(name = "l1_fee_scalar")]
142    fn py_l1_fee_scalar(&self) -> Option<u64> {
143        self.l1_fee_scalar
144    }
145
146    #[getter]
147    #[pyo3(name = "timestamp")]
148    fn py_timestamp(&self) -> u64 {
149        self.timestamp.as_u64()
150    }
151
152    #[getter]
153    #[pyo3(name = "ts_event")]
154    fn py_ts_event(&self) -> u64 {
155        self.timestamp.as_u64()
156    }
157
158    #[getter]
159    #[pyo3(name = "ts_init")]
160    fn py_ts_init(&self) -> u64 {
161        self.timestamp.as_u64()
162    }
163
164    fn __str__(&self) -> String {
165        self.to_string()
166    }
167
168    fn __repr__(&self) -> String {
169        format!("{self:?}")
170    }
171
172    fn __hash__(&self) -> u64 {
173        let mut hasher = DefaultHasher::new();
174        self.hash.hash(&mut hasher);
175        hasher.finish()
176    }
177}
178
179#[pymethods]
180#[pyo3_stub_gen::derive::gen_stub_pymethods]
181impl DefiData {
182    /// Returns the block number associated with this DeFi data.
183    #[getter]
184    #[pyo3(name = "block_number")]
185    fn py_block_number(&self) -> u64 {
186        self.block_number()
187    }
188
189    /// Returns the transaction index associated with this DeFi data.
190    #[getter]
191    #[pyo3(name = "transaction_index")]
192    fn py_transaction_index(&self) -> u32 {
193        self.transaction_index()
194    }
195
196    /// Returns the log index associated with this DeFi data.
197    #[getter]
198    #[pyo3(name = "log_index")]
199    fn py_log_index(&self) -> u32 {
200        self.log_index()
201    }
202
203    /// Returns the event timestamp associated with this DeFi data.
204    #[getter]
205    #[pyo3(name = "timestamp")]
206    fn py_timestamp(&self) -> u64 {
207        self.timestamp().as_u64()
208    }
209
210    /// Returns the event timestamp associated with this DeFi data.
211    #[getter]
212    #[pyo3(name = "ts_event")]
213    fn py_ts_event(&self) -> u64 {
214        self.ts_event().as_u64()
215    }
216
217    /// Returns the initialization timestamp associated with this DeFi data.
218    #[getter]
219    #[pyo3(name = "ts_init")]
220    fn py_ts_init(&self) -> u64 {
221        self.ts_init().as_u64()
222    }
223
224    /// Returns the block position associated with this DeFi data.
225    #[pyo3(name = "block_position")]
226    fn py_block_position(&self) -> (u64, u32, u32) {
227        self.block_position()
228    }
229}
230
231#[pymethods]
232#[pyo3_stub_gen::derive::gen_stub_pymethods]
233impl PoolSwap {
234    /// Represents a token swap transaction on a decentralized exchange (DEX).
235    ///
236    /// This structure captures both the raw blockchain data from a swap event and
237    /// optionally includes computed market-oriented trade information. It serves as
238    /// the primary data structure for tracking and analyzing DEX swap activity.
239    #[new]
240    #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
241    fn py_new(
242        chain: Chain,
243        dex: Dex,
244        instrument_id: InstrumentId,
245        pool_identifier: String,
246        block: u64,
247        transaction_hash: String,
248        transaction_index: u32,
249        log_index: u32,
250        timestamp: u64,
251        sender: String,
252        receiver: String,
253        amount0: String,
254        amount1: String,
255        sqrt_price_x96: String,
256        liquidity: u128,
257        tick: i32,
258    ) -> PyResult<Self> {
259        let sender = sender.parse().map_err(to_pyvalue_err)?;
260        let receiver = receiver.parse().map_err(to_pyvalue_err)?;
261        let amount0 = amount0.parse().map_err(to_pyvalue_err)?;
262        let amount1 = amount1.parse().map_err(to_pyvalue_err)?;
263        let sqrt_price_x96 = sqrt_price_x96.parse().map_err(to_pyvalue_err)?;
264        let pool_identifier = pool_identifier.parse().map_err(to_pyvalue_err)?;
265        Ok(Self::new(
266            Arc::new(chain),
267            Arc::new(dex),
268            instrument_id,
269            pool_identifier,
270            block,
271            transaction_hash,
272            transaction_index,
273            log_index,
274            timestamp.into(), // ts_event
275            timestamp.into(), // ts_init (single Python timestamp)
276            sender,
277            receiver,
278            amount0,
279            amount1,
280            sqrt_price_x96,
281            liquidity,
282            tick,
283        ))
284    }
285
286    fn __str__(&self) -> String {
287        self.to_string()
288    }
289
290    fn __repr__(&self) -> String {
291        format!("{self:?}")
292    }
293
294    fn __hash__(&self) -> u64 {
295        let mut hasher = DefaultHasher::new();
296        self.chain.chain_id.hash(&mut hasher);
297        self.transaction_hash.hash(&mut hasher);
298        self.log_index.hash(&mut hasher);
299        hasher.finish()
300    }
301
302    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
303        match op {
304            CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
305            CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
306            _ => py.NotImplemented(),
307        }
308    }
309
310    #[getter]
311    #[pyo3(name = "chain")]
312    fn py_chain(&self) -> Chain {
313        self.chain.as_ref().clone()
314    }
315
316    #[getter]
317    #[pyo3(name = "dex")]
318    fn py_dex(&self) -> Dex {
319        self.dex.as_ref().clone()
320    }
321
322    #[getter]
323    #[pyo3(name = "instrument_id")]
324    fn py_instrument_id(&self) -> InstrumentId {
325        self.instrument_id
326    }
327
328    #[getter]
329    #[pyo3(name = "pool_identifier")]
330    fn py_pool_identifier(&self) -> String {
331        self.pool_identifier.to_string()
332    }
333
334    #[getter]
335    #[pyo3(name = "block")]
336    fn py_block(&self) -> u64 {
337        self.block
338    }
339
340    #[getter]
341    #[pyo3(name = "transaction_hash")]
342    fn py_transaction_hash(&self) -> &str {
343        &self.transaction_hash
344    }
345
346    #[getter]
347    #[pyo3(name = "transaction_index")]
348    fn py_transaction_index(&self) -> u32 {
349        self.transaction_index
350    }
351
352    #[getter]
353    #[pyo3(name = "log_index")]
354    fn py_log_index(&self) -> u32 {
355        self.log_index
356    }
357
358    #[getter]
359    #[pyo3(name = "sender")]
360    fn py_sender(&self) -> String {
361        self.sender.to_string()
362    }
363
364    #[getter]
365    #[pyo3(name = "recipient")]
366    fn py_recipient(&self) -> String {
367        self.recipient.to_string()
368    }
369
370    #[getter]
371    #[pyo3(name = "amount0")]
372    fn py_amount0(&self) -> String {
373        self.amount0.to_string()
374    }
375
376    #[getter]
377    #[pyo3(name = "amount1")]
378    fn py_amount1(&self) -> String {
379        self.amount1.to_string()
380    }
381
382    #[getter]
383    #[pyo3(name = "sqrt_price_x96")]
384    fn py_sqrt_price_x96(&self) -> String {
385        self.sqrt_price_x96.to_string()
386    }
387
388    #[getter]
389    #[pyo3(name = "liquidity")]
390    fn py_liquidity(&self) -> String {
391        self.liquidity.to_string()
392    }
393
394    #[getter]
395    #[pyo3(name = "tick")]
396    fn py_tick(&self) -> i32 {
397        self.tick
398    }
399
400    #[getter]
401    #[pyo3(name = "timestamp")]
402    fn py_timestamp(&self) -> u64 {
403        self.ts_event.as_u64()
404    }
405
406    #[getter]
407    #[pyo3(name = "ts_event")]
408    fn py_ts_event(&self) -> u64 {
409        self.ts_event.as_u64()
410    }
411
412    #[getter]
413    #[pyo3(name = "ts_init")]
414    fn py_ts_init(&self) -> u64 {
415        self.ts_init.as_u64()
416    }
417}
418
419#[pymethods]
420#[pyo3_stub_gen::derive::gen_stub_pymethods]
421impl PoolLiquidityUpdate {
422    /// Represents a liquidity update event in a decentralized exchange (DEX) pool.
423    #[new]
424    #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
425    fn py_new(
426        chain: Chain,
427        dex: Dex,
428        pool_identifier: String,
429        instrument_id: InstrumentId,
430        kind: PoolLiquidityUpdateType,
431        block: u64,
432        transaction_hash: String,
433        transaction_index: u32,
434        log_index: u32,
435        sender: Option<String>,
436        owner: String,
437        position_liquidity: String,
438        amount0: String,
439        amount1: String,
440        tick_lower: i32,
441        tick_upper: i32,
442        timestamp: u64,
443    ) -> PyResult<Self> {
444        let sender = sender
445            .map(|s| s.parse())
446            .transpose()
447            .map_err(to_pyvalue_err)?;
448        let owner = owner.parse().map_err(to_pyvalue_err)?;
449        let position_liquidity = position_liquidity.parse().map_err(to_pyvalue_err)?;
450        let amount0 = amount0.parse().map_err(to_pyvalue_err)?;
451        let amount1 = amount1.parse().map_err(to_pyvalue_err)?;
452        let pool_identifier = pool_identifier.parse().map_err(to_pyvalue_err)?;
453        Ok(Self::new(
454            Arc::new(chain),
455            Arc::new(dex),
456            instrument_id,
457            pool_identifier,
458            kind,
459            block,
460            transaction_hash,
461            transaction_index,
462            log_index,
463            sender,
464            owner,
465            position_liquidity,
466            amount0,
467            amount1,
468            tick_lower,
469            tick_upper,
470            timestamp.into(), // ts_event
471            timestamp.into(), // ts_init (single Python timestamp)
472        ))
473    }
474
475    fn __str__(&self) -> String {
476        self.to_string()
477    }
478
479    fn __repr__(&self) -> String {
480        format!("{self:?}")
481    }
482
483    fn __hash__(&self) -> u64 {
484        let mut hasher = DefaultHasher::new();
485        self.chain.chain_id.hash(&mut hasher);
486        self.transaction_hash.hash(&mut hasher);
487        self.log_index.hash(&mut hasher);
488        hasher.finish()
489    }
490
491    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
492        match op {
493            CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
494            CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
495            _ => py.NotImplemented(),
496        }
497    }
498
499    #[getter]
500    #[pyo3(name = "chain")]
501    fn py_chain(&self) -> Chain {
502        self.chain.as_ref().clone()
503    }
504
505    #[getter]
506    #[pyo3(name = "dex")]
507    fn py_dex(&self) -> Dex {
508        self.dex.as_ref().clone()
509    }
510
511    #[getter]
512    #[pyo3(name = "instrument_id")]
513    fn py_instrument_id(&self) -> InstrumentId {
514        self.instrument_id
515    }
516
517    #[getter]
518    #[pyo3(name = "pool_identifier")]
519    fn py_pool_identifier(&self) -> String {
520        self.pool_identifier.to_string()
521    }
522
523    #[getter]
524    #[pyo3(name = "kind")]
525    fn py_kind(&self) -> PoolLiquidityUpdateType {
526        self.kind
527    }
528
529    #[getter]
530    #[pyo3(name = "block")]
531    fn py_block(&self) -> u64 {
532        self.block
533    }
534
535    #[getter]
536    #[pyo3(name = "transaction_hash")]
537    fn py_transaction_hash(&self) -> &str {
538        &self.transaction_hash
539    }
540
541    #[getter]
542    #[pyo3(name = "transaction_index")]
543    fn py_transaction_index(&self) -> u32 {
544        self.transaction_index
545    }
546
547    #[getter]
548    #[pyo3(name = "log_index")]
549    fn py_log_index(&self) -> u32 {
550        self.log_index
551    }
552
553    #[getter]
554    #[pyo3(name = "sender")]
555    fn py_sender(&self) -> Option<String> {
556        self.sender.map(|s| s.to_string())
557    }
558
559    #[getter]
560    #[pyo3(name = "owner")]
561    fn py_owner(&self) -> String {
562        self.owner.to_string()
563    }
564
565    #[getter]
566    #[pyo3(name = "position_liquidity")]
567    fn py_position_liquidity(&self) -> String {
568        self.position_liquidity.to_string()
569    }
570
571    #[getter]
572    #[pyo3(name = "amount0")]
573    fn py_amount0(&self) -> String {
574        self.amount0.to_string()
575    }
576
577    #[getter]
578    #[pyo3(name = "amount1")]
579    fn py_amount1(&self) -> String {
580        self.amount1.to_string()
581    }
582
583    #[getter]
584    #[pyo3(name = "tick_lower")]
585    fn py_tick_lower(&self) -> i32 {
586        self.tick_lower
587    }
588
589    #[getter]
590    #[pyo3(name = "tick_upper")]
591    fn py_tick_upper(&self) -> i32 {
592        self.tick_upper
593    }
594
595    #[getter]
596    #[pyo3(name = "timestamp")]
597    fn py_timestamp(&self) -> u64 {
598        self.ts_event.as_u64()
599    }
600
601    #[getter]
602    #[pyo3(name = "ts_event")]
603    fn py_ts_event(&self) -> u64 {
604        self.ts_event.as_u64()
605    }
606
607    #[getter]
608    #[pyo3(name = "ts_init")]
609    fn py_ts_init(&self) -> u64 {
610        self.ts_init.as_u64()
611    }
612}
613
614#[pymethods]
615#[pyo3_stub_gen::derive::gen_stub_pymethods]
616impl PoolFeeCollect {
617    /// Represents a fee collection event in a decentralized exchange (DEX) pool.
618    #[new]
619    #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
620    fn py_new(
621        chain: Chain,
622        dex: Dex,
623        pool_identifier: String,
624        instrument_id: InstrumentId,
625        block: u64,
626        transaction_hash: String,
627        transaction_index: u32,
628        log_index: u32,
629        owner: String,
630        amount0: String,
631        amount1: String,
632        tick_lower: i32,
633        tick_upper: i32,
634        timestamp: u64,
635    ) -> PyResult<Self> {
636        let owner = owner.parse().map_err(to_pyvalue_err)?;
637        let amount0 = amount0.parse().map_err(to_pyvalue_err)?;
638        let amount1 = amount1.parse().map_err(to_pyvalue_err)?;
639        let pool_identifier = pool_identifier.parse().map_err(to_pyvalue_err)?;
640        Ok(Self::new(
641            Arc::new(chain),
642            Arc::new(dex),
643            instrument_id,
644            pool_identifier,
645            block,
646            transaction_hash,
647            transaction_index,
648            log_index,
649            owner,
650            amount0,
651            amount1,
652            tick_lower,
653            tick_upper,
654            timestamp.into(), // ts_event
655            timestamp.into(), // ts_init (single Python timestamp)
656        ))
657    }
658
659    fn __str__(&self) -> String {
660        self.to_string()
661    }
662
663    fn __repr__(&self) -> String {
664        format!("{self:?}")
665    }
666
667    fn __hash__(&self) -> u64 {
668        let mut hasher = DefaultHasher::new();
669        self.chain.chain_id.hash(&mut hasher);
670        self.transaction_hash.hash(&mut hasher);
671        self.log_index.hash(&mut hasher);
672        hasher.finish()
673    }
674
675    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
676        match op {
677            CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
678            CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
679            _ => py.NotImplemented(),
680        }
681    }
682
683    #[getter]
684    #[pyo3(name = "chain")]
685    fn py_chain(&self) -> Chain {
686        self.chain.as_ref().clone()
687    }
688
689    #[getter]
690    #[pyo3(name = "dex")]
691    fn py_dex(&self) -> Dex {
692        self.dex.as_ref().clone()
693    }
694
695    #[getter]
696    #[pyo3(name = "instrument_id")]
697    fn py_instrument_id(&self) -> InstrumentId {
698        self.instrument_id
699    }
700
701    #[getter]
702    #[pyo3(name = "pool_identifier")]
703    fn py_pool_identifier(&self) -> String {
704        self.pool_identifier.to_string()
705    }
706
707    #[getter]
708    #[pyo3(name = "block")]
709    fn py_block(&self) -> u64 {
710        self.block
711    }
712
713    #[getter]
714    #[pyo3(name = "transaction_hash")]
715    fn py_transaction_hash(&self) -> &str {
716        &self.transaction_hash
717    }
718
719    #[getter]
720    #[pyo3(name = "transaction_index")]
721    fn py_transaction_index(&self) -> u32 {
722        self.transaction_index
723    }
724
725    #[getter]
726    #[pyo3(name = "log_index")]
727    fn py_log_index(&self) -> u32 {
728        self.log_index
729    }
730
731    #[getter]
732    #[pyo3(name = "owner")]
733    fn py_owner(&self) -> String {
734        self.owner.to_string()
735    }
736
737    #[getter]
738    #[pyo3(name = "amount0")]
739    fn py_amount0(&self) -> String {
740        self.amount0.to_string()
741    }
742
743    #[getter]
744    #[pyo3(name = "amount1")]
745    fn py_amount1(&self) -> String {
746        self.amount1.to_string()
747    }
748
749    #[getter]
750    #[pyo3(name = "tick_lower")]
751    fn py_tick_lower(&self) -> i32 {
752        self.tick_lower
753    }
754
755    #[getter]
756    #[pyo3(name = "tick_upper")]
757    fn py_tick_upper(&self) -> i32 {
758        self.tick_upper
759    }
760
761    #[getter]
762    #[pyo3(name = "timestamp")]
763    fn py_timestamp(&self) -> u64 {
764        self.ts_event.as_u64()
765    }
766
767    #[getter]
768    #[pyo3(name = "ts_event")]
769    fn py_ts_event(&self) -> u64 {
770        self.ts_event.as_u64()
771    }
772
773    #[getter]
774    #[pyo3(name = "ts_init")]
775    fn py_ts_init(&self) -> u64 {
776        self.ts_init.as_u64()
777    }
778}
779
780#[pymethods]
781#[pyo3_stub_gen::derive::gen_stub_pymethods]
782impl PoolFeeProtocolUpdate {
783    /// Represents a protocol-fee configuration change in a Uniswap V3-style pool.
784    ///
785    /// Emitted by `SetFeeProtocol`, this carries the new protocol-fee values for each token. Uniswap
786    /// V3 uses 4-bit denominators, while PancakeSwap V3 uses `uint32` basis-point shares. Only the new
787    /// values are kept; the previous values in the event are not needed to rebuild state.
788    #[new]
789    #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
790    fn py_new(
791        chain: Chain,
792        dex: Dex,
793        pool_identifier: String,
794        instrument_id: InstrumentId,
795        block: u64,
796        transaction_hash: String,
797        transaction_index: u32,
798        log_index: u32,
799        fee_protocol0_new: u32,
800        fee_protocol1_new: u32,
801        timestamp: u64,
802    ) -> PyResult<Self> {
803        let pool_identifier = pool_identifier.parse().map_err(to_pyvalue_err)?;
804        Ok(Self::new(
805            Arc::new(chain),
806            Arc::new(dex),
807            instrument_id,
808            pool_identifier,
809            block,
810            transaction_hash,
811            transaction_index,
812            log_index,
813            fee_protocol0_new,
814            fee_protocol1_new,
815            timestamp.into(), // ts_event
816            timestamp.into(), // ts_init (single Python timestamp)
817        ))
818    }
819
820    fn __str__(&self) -> String {
821        self.to_string()
822    }
823
824    fn __repr__(&self) -> String {
825        format!("{self:?}")
826    }
827
828    fn __hash__(&self) -> u64 {
829        let mut hasher = DefaultHasher::new();
830        self.chain.chain_id.hash(&mut hasher);
831        self.transaction_hash.hash(&mut hasher);
832        self.log_index.hash(&mut hasher);
833        hasher.finish()
834    }
835
836    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
837        match op {
838            CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
839            CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
840            _ => py.NotImplemented(),
841        }
842    }
843
844    #[getter]
845    #[pyo3(name = "chain")]
846    fn py_chain(&self) -> Chain {
847        self.chain.as_ref().clone()
848    }
849
850    #[getter]
851    #[pyo3(name = "dex")]
852    fn py_dex(&self) -> Dex {
853        self.dex.as_ref().clone()
854    }
855
856    #[getter]
857    #[pyo3(name = "instrument_id")]
858    fn py_instrument_id(&self) -> InstrumentId {
859        self.instrument_id
860    }
861
862    #[getter]
863    #[pyo3(name = "pool_identifier")]
864    fn py_pool_identifier(&self) -> String {
865        self.pool_identifier.to_string()
866    }
867
868    #[getter]
869    #[pyo3(name = "block")]
870    fn py_block(&self) -> u64 {
871        self.block
872    }
873
874    #[getter]
875    #[pyo3(name = "transaction_hash")]
876    fn py_transaction_hash(&self) -> &str {
877        &self.transaction_hash
878    }
879
880    #[getter]
881    #[pyo3(name = "transaction_index")]
882    fn py_transaction_index(&self) -> u32 {
883        self.transaction_index
884    }
885
886    #[getter]
887    #[pyo3(name = "log_index")]
888    fn py_log_index(&self) -> u32 {
889        self.log_index
890    }
891
892    #[getter]
893    #[pyo3(name = "fee_protocol0_new")]
894    fn py_fee_protocol0_new(&self) -> u32 {
895        self.fee_protocol0_new
896    }
897
898    #[getter]
899    #[pyo3(name = "fee_protocol1_new")]
900    fn py_fee_protocol1_new(&self) -> u32 {
901        self.fee_protocol1_new
902    }
903
904    #[getter]
905    #[pyo3(name = "timestamp")]
906    fn py_timestamp(&self) -> u64 {
907        self.ts_event.as_u64()
908    }
909
910    #[getter]
911    #[pyo3(name = "ts_event")]
912    fn py_ts_event(&self) -> u64 {
913        self.ts_event.as_u64()
914    }
915
916    #[getter]
917    #[pyo3(name = "ts_init")]
918    fn py_ts_init(&self) -> u64 {
919        self.ts_init.as_u64()
920    }
921}
922
923#[pymethods]
924#[pyo3_stub_gen::derive::gen_stub_pymethods]
925impl PoolFeeProtocolCollect {
926    /// Represents a protocol-fee withdrawal from a Uniswap V3-style pool.
927    ///
928    /// Emitted by `CollectProtocol`, this carries the protocol-fee amounts withdrawn to the recipient.
929    /// The amounts decrement the pool's accrued protocol-fee balances, leaving the on-chain remainder
930    /// (Uniswap V3 keeps one wei in each slot to save gas).
931    #[new]
932    #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
933    fn py_new(
934        chain: Chain,
935        dex: Dex,
936        pool_identifier: String,
937        instrument_id: InstrumentId,
938        block: u64,
939        transaction_hash: String,
940        transaction_index: u32,
941        log_index: u32,
942        sender: String,
943        recipient: String,
944        amount0: String,
945        amount1: String,
946        timestamp: u64,
947    ) -> PyResult<Self> {
948        let sender = sender.parse().map_err(to_pyvalue_err)?;
949        let recipient = recipient.parse().map_err(to_pyvalue_err)?;
950        let amount0 = amount0.parse().map_err(to_pyvalue_err)?;
951        let amount1 = amount1.parse().map_err(to_pyvalue_err)?;
952        let pool_identifier = pool_identifier.parse().map_err(to_pyvalue_err)?;
953        Ok(Self::new(
954            Arc::new(chain),
955            Arc::new(dex),
956            instrument_id,
957            pool_identifier,
958            block,
959            transaction_hash,
960            transaction_index,
961            log_index,
962            sender,
963            recipient,
964            amount0,
965            amount1,
966            timestamp.into(), // ts_event
967            timestamp.into(), // ts_init (single Python timestamp)
968        ))
969    }
970
971    fn __str__(&self) -> String {
972        self.to_string()
973    }
974
975    fn __repr__(&self) -> String {
976        format!("{self:?}")
977    }
978
979    fn __hash__(&self) -> u64 {
980        let mut hasher = DefaultHasher::new();
981        self.chain.chain_id.hash(&mut hasher);
982        self.transaction_hash.hash(&mut hasher);
983        self.log_index.hash(&mut hasher);
984        hasher.finish()
985    }
986
987    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
988        match op {
989            CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
990            CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
991            _ => py.NotImplemented(),
992        }
993    }
994
995    #[getter]
996    #[pyo3(name = "chain")]
997    fn py_chain(&self) -> Chain {
998        self.chain.as_ref().clone()
999    }
1000
1001    #[getter]
1002    #[pyo3(name = "dex")]
1003    fn py_dex(&self) -> Dex {
1004        self.dex.as_ref().clone()
1005    }
1006
1007    #[getter]
1008    #[pyo3(name = "instrument_id")]
1009    fn py_instrument_id(&self) -> InstrumentId {
1010        self.instrument_id
1011    }
1012
1013    #[getter]
1014    #[pyo3(name = "pool_identifier")]
1015    fn py_pool_identifier(&self) -> String {
1016        self.pool_identifier.to_string()
1017    }
1018
1019    #[getter]
1020    #[pyo3(name = "block")]
1021    fn py_block(&self) -> u64 {
1022        self.block
1023    }
1024
1025    #[getter]
1026    #[pyo3(name = "transaction_hash")]
1027    fn py_transaction_hash(&self) -> &str {
1028        &self.transaction_hash
1029    }
1030
1031    #[getter]
1032    #[pyo3(name = "transaction_index")]
1033    fn py_transaction_index(&self) -> u32 {
1034        self.transaction_index
1035    }
1036
1037    #[getter]
1038    #[pyo3(name = "log_index")]
1039    fn py_log_index(&self) -> u32 {
1040        self.log_index
1041    }
1042
1043    #[getter]
1044    #[pyo3(name = "sender")]
1045    fn py_sender(&self) -> String {
1046        self.sender.to_string()
1047    }
1048
1049    #[getter]
1050    #[pyo3(name = "recipient")]
1051    fn py_recipient(&self) -> String {
1052        self.recipient.to_string()
1053    }
1054
1055    #[getter]
1056    #[pyo3(name = "amount0")]
1057    fn py_amount0(&self) -> String {
1058        self.amount0.to_string()
1059    }
1060
1061    #[getter]
1062    #[pyo3(name = "amount1")]
1063    fn py_amount1(&self) -> String {
1064        self.amount1.to_string()
1065    }
1066
1067    #[getter]
1068    #[pyo3(name = "timestamp")]
1069    fn py_timestamp(&self) -> u64 {
1070        self.ts_event.as_u64()
1071    }
1072
1073    #[getter]
1074    #[pyo3(name = "ts_event")]
1075    fn py_ts_event(&self) -> u64 {
1076        self.ts_event.as_u64()
1077    }
1078
1079    #[getter]
1080    #[pyo3(name = "ts_init")]
1081    fn py_ts_init(&self) -> u64 {
1082        self.ts_init.as_u64()
1083    }
1084}
1085
1086#[pymethods]
1087#[pyo3_stub_gen::derive::gen_stub_pymethods]
1088impl PoolFlash {
1089    /// Represents a flash loan event from a Uniswap V3 pool.
1090    ///
1091    /// Flash loans allow users to borrow tokens without collateral as long as they are returned
1092    /// within the same transaction. Fees are paid on the borrowed amount, which are added to
1093    /// the pool's fee growth accumulators.
1094    #[new]
1095    #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
1096    fn py_new(
1097        chain: Chain,
1098        dex: Dex,
1099        pool_identifier: String,
1100        instrument_id: InstrumentId,
1101        block: u64,
1102        transaction_hash: String,
1103        transaction_index: u32,
1104        log_index: u32,
1105        sender: String,
1106        recipient: String,
1107        amount0: String,
1108        amount1: String,
1109        paid0: String,
1110        paid1: String,
1111        timestamp: u64,
1112    ) -> PyResult<Self> {
1113        let sender = sender.parse().map_err(to_pyvalue_err)?;
1114        let recipient = recipient.parse().map_err(to_pyvalue_err)?;
1115        let amount0 = amount0.parse().map_err(to_pyvalue_err)?;
1116        let amount1 = amount1.parse().map_err(to_pyvalue_err)?;
1117        let paid0 = paid0.parse().map_err(to_pyvalue_err)?;
1118        let paid1 = paid1.parse().map_err(to_pyvalue_err)?;
1119        let pool_identifier = pool_identifier.parse().map_err(to_pyvalue_err)?;
1120        Ok(Self::new(
1121            Arc::new(chain),
1122            Arc::new(dex),
1123            instrument_id,
1124            pool_identifier,
1125            block,
1126            transaction_hash,
1127            transaction_index,
1128            log_index,
1129            timestamp.into(), // ts_event
1130            timestamp.into(), // ts_init (single Python timestamp)
1131            sender,
1132            recipient,
1133            amount0,
1134            amount1,
1135            paid0,
1136            paid1,
1137        ))
1138    }
1139
1140    fn __str__(&self) -> String {
1141        self.to_string()
1142    }
1143
1144    fn __repr__(&self) -> String {
1145        format!("{self:?}")
1146    }
1147
1148    fn __hash__(&self) -> u64 {
1149        let mut hasher = DefaultHasher::new();
1150        self.chain.chain_id.hash(&mut hasher);
1151        self.transaction_hash.hash(&mut hasher);
1152        self.log_index.hash(&mut hasher);
1153        hasher.finish()
1154    }
1155
1156    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
1157        match op {
1158            CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
1159            CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
1160            _ => py.NotImplemented(),
1161        }
1162    }
1163
1164    #[getter]
1165    #[pyo3(name = "chain")]
1166    fn py_chain(&self) -> Chain {
1167        self.chain.as_ref().clone()
1168    }
1169
1170    #[getter]
1171    #[pyo3(name = "dex")]
1172    fn py_dex(&self) -> Dex {
1173        self.dex.as_ref().clone()
1174    }
1175
1176    #[getter]
1177    #[pyo3(name = "instrument_id")]
1178    fn py_instrument_id(&self) -> InstrumentId {
1179        self.instrument_id
1180    }
1181
1182    #[getter]
1183    #[pyo3(name = "pool_identifier")]
1184    fn py_pool_identifier(&self) -> String {
1185        self.pool_identifier.to_string()
1186    }
1187
1188    #[getter]
1189    #[pyo3(name = "block")]
1190    fn py_block(&self) -> u64 {
1191        self.block
1192    }
1193
1194    #[getter]
1195    #[pyo3(name = "transaction_hash")]
1196    fn py_transaction_hash(&self) -> &str {
1197        &self.transaction_hash
1198    }
1199
1200    #[getter]
1201    #[pyo3(name = "transaction_index")]
1202    fn py_transaction_index(&self) -> u32 {
1203        self.transaction_index
1204    }
1205
1206    #[getter]
1207    #[pyo3(name = "log_index")]
1208    fn py_log_index(&self) -> u32 {
1209        self.log_index
1210    }
1211
1212    #[getter]
1213    #[pyo3(name = "sender")]
1214    fn py_sender(&self) -> String {
1215        self.sender.to_string()
1216    }
1217
1218    #[getter]
1219    #[pyo3(name = "recipient")]
1220    fn py_recipient(&self) -> String {
1221        self.recipient.to_string()
1222    }
1223
1224    #[getter]
1225    #[pyo3(name = "amount0")]
1226    fn py_amount0(&self) -> String {
1227        self.amount0.to_string()
1228    }
1229
1230    #[getter]
1231    #[pyo3(name = "amount1")]
1232    fn py_amount1(&self) -> String {
1233        self.amount1.to_string()
1234    }
1235
1236    #[getter]
1237    #[pyo3(name = "paid0")]
1238    fn py_paid0(&self) -> String {
1239        self.paid0.to_string()
1240    }
1241
1242    #[getter]
1243    #[pyo3(name = "paid1")]
1244    fn py_paid1(&self) -> String {
1245        self.paid1.to_string()
1246    }
1247
1248    #[getter]
1249    #[pyo3(name = "timestamp")]
1250    fn py_timestamp(&self) -> u64 {
1251        self.ts_event.as_u64()
1252    }
1253
1254    #[getter]
1255    #[pyo3(name = "ts_event")]
1256    fn py_ts_event(&self) -> u64 {
1257        self.ts_event.as_u64()
1258    }
1259
1260    #[getter]
1261    #[pyo3(name = "ts_init")]
1262    fn py_ts_init(&self) -> u64 {
1263        self.ts_init.as_u64()
1264    }
1265}
1266
1267#[pymethods]
1268#[pyo3_stub_gen::derive::gen_stub_pymethods]
1269impl Transaction {
1270    /// Represents a transaction on an EVM based blockchain.
1271    #[new]
1272    #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
1273    fn py_new(
1274        chain: Chain,
1275        hash: String,
1276        block_hash: String,
1277        block_number: u64,
1278        from: String,
1279        to: String,
1280        gas: String,
1281        gas_price: String,
1282        transaction_index: u64,
1283        value: String,
1284    ) -> PyResult<Self> {
1285        let from = from.parse().map_err(to_pyvalue_err)?;
1286        let to = to.parse().map_err(to_pyvalue_err)?;
1287        let gas = gas.parse().map_err(to_pyvalue_err)?;
1288        let gas_price = gas_price.parse().map_err(to_pyvalue_err)?;
1289        let value = value.parse().map_err(to_pyvalue_err)?;
1290        Ok(Self::new(
1291            chain,
1292            hash,
1293            block_hash,
1294            block_number,
1295            from,
1296            to,
1297            gas,
1298            gas_price,
1299            transaction_index,
1300            value,
1301        ))
1302    }
1303
1304    fn __str__(&self) -> String {
1305        format!(
1306            "Transaction(chain={}, hash={}, block_number={}, from={}, to={}, value={})",
1307            self.chain.name, self.hash, self.block_number, self.from, self.to, self.value
1308        )
1309    }
1310
1311    fn __repr__(&self) -> String {
1312        format!("{self:?}")
1313    }
1314
1315    fn __hash__(&self) -> u64 {
1316        let mut hasher = DefaultHasher::new();
1317        self.hash.hash(&mut hasher);
1318        hasher.finish()
1319    }
1320
1321    fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
1322        match op {
1323            CompareOp::Eq => self.hash.eq(&other.hash).into_py_any_unwrap(py),
1324            CompareOp::Ne => self.hash.ne(&other.hash).into_py_any_unwrap(py),
1325            _ => py.NotImplemented(),
1326        }
1327    }
1328
1329    #[getter]
1330    #[pyo3(name = "chain")]
1331    fn py_chain(&self) -> Chain {
1332        self.chain.clone()
1333    }
1334
1335    #[getter]
1336    #[pyo3(name = "hash")]
1337    fn py_hash(&self) -> &str {
1338        &self.hash
1339    }
1340
1341    #[getter]
1342    #[pyo3(name = "block_hash")]
1343    fn py_block_hash(&self) -> &str {
1344        &self.block_hash
1345    }
1346
1347    #[getter]
1348    #[pyo3(name = "block_number")]
1349    fn py_block_number(&self) -> u64 {
1350        self.block_number
1351    }
1352
1353    #[getter]
1354    #[pyo3(name = "from")]
1355    fn py_from(&self) -> String {
1356        self.from.to_string()
1357    }
1358
1359    #[getter]
1360    #[pyo3(name = "to")]
1361    fn py_to(&self) -> String {
1362        self.to.to_string()
1363    }
1364
1365    #[getter]
1366    #[pyo3(name = "value")]
1367    fn py_value(&self) -> String {
1368        self.value.to_string()
1369    }
1370
1371    #[getter]
1372    #[pyo3(name = "transaction_index")]
1373    fn py_transaction_index(&self) -> u64 {
1374        self.transaction_index
1375    }
1376
1377    #[getter]
1378    #[pyo3(name = "gas")]
1379    fn py_gas(&self) -> String {
1380        self.gas.to_string()
1381    }
1382
1383    #[getter]
1384    #[pyo3(name = "gas_price")]
1385    fn py_gas_price(&self) -> String {
1386        self.gas_price.to_string()
1387    }
1388}