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::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) -> bool {
303        match op {
304            CompareOp::Eq => self == other,
305            CompareOp::Ne => self != other,
306            _ => panic!("Unsupported comparison for PoolSwap"),
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: pyo3::pyclass::CompareOp) -> bool {
492        match op {
493            CompareOp::Eq => self == other,
494            CompareOp::Ne => self != other,
495            _ => panic!("Unsupported comparison for PoolLiquidityUpdate"),
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) -> bool {
676        match op {
677            CompareOp::Eq => self == other,
678            CompareOp::Ne => self != other,
679            _ => panic!("Unsupported comparison for PoolFeeCollect"),
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 denominators for each token.
786    /// Only the new values are kept; the previous values in the event are not needed to rebuild state.
787    #[new]
788    #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
789    fn py_new(
790        chain: Chain,
791        dex: Dex,
792        pool_identifier: String,
793        instrument_id: InstrumentId,
794        block: u64,
795        transaction_hash: String,
796        transaction_index: u32,
797        log_index: u32,
798        fee_protocol0_new: u8,
799        fee_protocol1_new: u8,
800        timestamp: u64,
801    ) -> PyResult<Self> {
802        let pool_identifier = pool_identifier.parse().map_err(to_pyvalue_err)?;
803        Ok(Self::new(
804            Arc::new(chain),
805            Arc::new(dex),
806            instrument_id,
807            pool_identifier,
808            block,
809            transaction_hash,
810            transaction_index,
811            log_index,
812            fee_protocol0_new,
813            fee_protocol1_new,
814            timestamp.into(), // ts_event
815            timestamp.into(), // ts_init (single Python timestamp)
816        ))
817    }
818
819    fn __str__(&self) -> String {
820        self.to_string()
821    }
822
823    fn __repr__(&self) -> String {
824        format!("{self:?}")
825    }
826
827    fn __hash__(&self) -> u64 {
828        let mut hasher = DefaultHasher::new();
829        self.chain.chain_id.hash(&mut hasher);
830        self.transaction_hash.hash(&mut hasher);
831        self.log_index.hash(&mut hasher);
832        hasher.finish()
833    }
834
835    fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
836        match op {
837            CompareOp::Eq => self == other,
838            CompareOp::Ne => self != other,
839            _ => panic!("Unsupported comparison for PoolFeeProtocolUpdate"),
840        }
841    }
842
843    #[getter]
844    #[pyo3(name = "chain")]
845    fn py_chain(&self) -> Chain {
846        self.chain.as_ref().clone()
847    }
848
849    #[getter]
850    #[pyo3(name = "dex")]
851    fn py_dex(&self) -> Dex {
852        self.dex.as_ref().clone()
853    }
854
855    #[getter]
856    #[pyo3(name = "instrument_id")]
857    fn py_instrument_id(&self) -> InstrumentId {
858        self.instrument_id
859    }
860
861    #[getter]
862    #[pyo3(name = "pool_identifier")]
863    fn py_pool_identifier(&self) -> String {
864        self.pool_identifier.to_string()
865    }
866
867    #[getter]
868    #[pyo3(name = "block")]
869    fn py_block(&self) -> u64 {
870        self.block
871    }
872
873    #[getter]
874    #[pyo3(name = "transaction_hash")]
875    fn py_transaction_hash(&self) -> &str {
876        &self.transaction_hash
877    }
878
879    #[getter]
880    #[pyo3(name = "transaction_index")]
881    fn py_transaction_index(&self) -> u32 {
882        self.transaction_index
883    }
884
885    #[getter]
886    #[pyo3(name = "log_index")]
887    fn py_log_index(&self) -> u32 {
888        self.log_index
889    }
890
891    #[getter]
892    #[pyo3(name = "fee_protocol0_new")]
893    fn py_fee_protocol0_new(&self) -> u8 {
894        self.fee_protocol0_new
895    }
896
897    #[getter]
898    #[pyo3(name = "fee_protocol1_new")]
899    fn py_fee_protocol1_new(&self) -> u8 {
900        self.fee_protocol1_new
901    }
902
903    #[getter]
904    #[pyo3(name = "timestamp")]
905    fn py_timestamp(&self) -> u64 {
906        self.ts_event.as_u64()
907    }
908
909    #[getter]
910    #[pyo3(name = "ts_event")]
911    fn py_ts_event(&self) -> u64 {
912        self.ts_event.as_u64()
913    }
914
915    #[getter]
916    #[pyo3(name = "ts_init")]
917    fn py_ts_init(&self) -> u64 {
918        self.ts_init.as_u64()
919    }
920}
921
922#[pymethods]
923#[pyo3_stub_gen::derive::gen_stub_pymethods]
924impl PoolFeeProtocolCollect {
925    /// Represents a protocol-fee withdrawal from a Uniswap V3-style pool.
926    ///
927    /// Emitted by `CollectProtocol`, this carries the protocol-fee amounts withdrawn to the recipient.
928    /// The amounts decrement the pool's accrued protocol-fee balances, leaving the on-chain remainder
929    /// (Uniswap V3 keeps one wei in each slot to save gas).
930    #[new]
931    #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
932    fn py_new(
933        chain: Chain,
934        dex: Dex,
935        pool_identifier: String,
936        instrument_id: InstrumentId,
937        block: u64,
938        transaction_hash: String,
939        transaction_index: u32,
940        log_index: u32,
941        sender: String,
942        recipient: String,
943        amount0: String,
944        amount1: String,
945        timestamp: u64,
946    ) -> PyResult<Self> {
947        let sender = sender.parse().map_err(to_pyvalue_err)?;
948        let recipient = recipient.parse().map_err(to_pyvalue_err)?;
949        let amount0 = amount0.parse().map_err(to_pyvalue_err)?;
950        let amount1 = amount1.parse().map_err(to_pyvalue_err)?;
951        let pool_identifier = pool_identifier.parse().map_err(to_pyvalue_err)?;
952        Ok(Self::new(
953            Arc::new(chain),
954            Arc::new(dex),
955            instrument_id,
956            pool_identifier,
957            block,
958            transaction_hash,
959            transaction_index,
960            log_index,
961            sender,
962            recipient,
963            amount0,
964            amount1,
965            timestamp.into(), // ts_event
966            timestamp.into(), // ts_init (single Python timestamp)
967        ))
968    }
969
970    fn __str__(&self) -> String {
971        self.to_string()
972    }
973
974    fn __repr__(&self) -> String {
975        format!("{self:?}")
976    }
977
978    fn __hash__(&self) -> u64 {
979        let mut hasher = DefaultHasher::new();
980        self.chain.chain_id.hash(&mut hasher);
981        self.transaction_hash.hash(&mut hasher);
982        self.log_index.hash(&mut hasher);
983        hasher.finish()
984    }
985
986    fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
987        match op {
988            CompareOp::Eq => self == other,
989            CompareOp::Ne => self != other,
990            _ => panic!("Unsupported comparison for PoolFeeProtocolCollect"),
991        }
992    }
993
994    #[getter]
995    #[pyo3(name = "chain")]
996    fn py_chain(&self) -> Chain {
997        self.chain.as_ref().clone()
998    }
999
1000    #[getter]
1001    #[pyo3(name = "dex")]
1002    fn py_dex(&self) -> Dex {
1003        self.dex.as_ref().clone()
1004    }
1005
1006    #[getter]
1007    #[pyo3(name = "instrument_id")]
1008    fn py_instrument_id(&self) -> InstrumentId {
1009        self.instrument_id
1010    }
1011
1012    #[getter]
1013    #[pyo3(name = "pool_identifier")]
1014    fn py_pool_identifier(&self) -> String {
1015        self.pool_identifier.to_string()
1016    }
1017
1018    #[getter]
1019    #[pyo3(name = "block")]
1020    fn py_block(&self) -> u64 {
1021        self.block
1022    }
1023
1024    #[getter]
1025    #[pyo3(name = "transaction_hash")]
1026    fn py_transaction_hash(&self) -> &str {
1027        &self.transaction_hash
1028    }
1029
1030    #[getter]
1031    #[pyo3(name = "transaction_index")]
1032    fn py_transaction_index(&self) -> u32 {
1033        self.transaction_index
1034    }
1035
1036    #[getter]
1037    #[pyo3(name = "log_index")]
1038    fn py_log_index(&self) -> u32 {
1039        self.log_index
1040    }
1041
1042    #[getter]
1043    #[pyo3(name = "sender")]
1044    fn py_sender(&self) -> String {
1045        self.sender.to_string()
1046    }
1047
1048    #[getter]
1049    #[pyo3(name = "recipient")]
1050    fn py_recipient(&self) -> String {
1051        self.recipient.to_string()
1052    }
1053
1054    #[getter]
1055    #[pyo3(name = "amount0")]
1056    fn py_amount0(&self) -> String {
1057        self.amount0.to_string()
1058    }
1059
1060    #[getter]
1061    #[pyo3(name = "amount1")]
1062    fn py_amount1(&self) -> String {
1063        self.amount1.to_string()
1064    }
1065
1066    #[getter]
1067    #[pyo3(name = "timestamp")]
1068    fn py_timestamp(&self) -> u64 {
1069        self.ts_event.as_u64()
1070    }
1071
1072    #[getter]
1073    #[pyo3(name = "ts_event")]
1074    fn py_ts_event(&self) -> u64 {
1075        self.ts_event.as_u64()
1076    }
1077
1078    #[getter]
1079    #[pyo3(name = "ts_init")]
1080    fn py_ts_init(&self) -> u64 {
1081        self.ts_init.as_u64()
1082    }
1083}
1084
1085#[pymethods]
1086#[pyo3_stub_gen::derive::gen_stub_pymethods]
1087impl PoolFlash {
1088    /// Represents a flash loan event from a Uniswap V3 pool.
1089    ///
1090    /// Flash loans allow users to borrow tokens without collateral as long as they are returned
1091    /// within the same transaction. Fees are paid on the borrowed amount, which are added to
1092    /// the pool's fee growth accumulators.
1093    #[new]
1094    #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
1095    fn py_new(
1096        chain: Chain,
1097        dex: Dex,
1098        pool_identifier: String,
1099        instrument_id: InstrumentId,
1100        block: u64,
1101        transaction_hash: String,
1102        transaction_index: u32,
1103        log_index: u32,
1104        sender: String,
1105        recipient: String,
1106        amount0: String,
1107        amount1: String,
1108        paid0: String,
1109        paid1: String,
1110        timestamp: u64,
1111    ) -> PyResult<Self> {
1112        let sender = sender.parse().map_err(to_pyvalue_err)?;
1113        let recipient = recipient.parse().map_err(to_pyvalue_err)?;
1114        let amount0 = amount0.parse().map_err(to_pyvalue_err)?;
1115        let amount1 = amount1.parse().map_err(to_pyvalue_err)?;
1116        let paid0 = paid0.parse().map_err(to_pyvalue_err)?;
1117        let paid1 = paid1.parse().map_err(to_pyvalue_err)?;
1118        let pool_identifier = pool_identifier.parse().map_err(to_pyvalue_err)?;
1119        Ok(Self::new(
1120            Arc::new(chain),
1121            Arc::new(dex),
1122            instrument_id,
1123            pool_identifier,
1124            block,
1125            transaction_hash,
1126            transaction_index,
1127            log_index,
1128            timestamp.into(), // ts_event
1129            timestamp.into(), // ts_init (single Python timestamp)
1130            sender,
1131            recipient,
1132            amount0,
1133            amount1,
1134            paid0,
1135            paid1,
1136        ))
1137    }
1138
1139    fn __str__(&self) -> String {
1140        self.to_string()
1141    }
1142
1143    fn __repr__(&self) -> String {
1144        format!("{self:?}")
1145    }
1146
1147    fn __hash__(&self) -> u64 {
1148        let mut hasher = DefaultHasher::new();
1149        self.chain.chain_id.hash(&mut hasher);
1150        self.transaction_hash.hash(&mut hasher);
1151        self.log_index.hash(&mut hasher);
1152        hasher.finish()
1153    }
1154
1155    fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
1156        match op {
1157            CompareOp::Eq => self == other,
1158            CompareOp::Ne => self != other,
1159            _ => panic!("Unsupported comparison for PoolFlash"),
1160        }
1161    }
1162
1163    #[getter]
1164    #[pyo3(name = "chain")]
1165    fn py_chain(&self) -> Chain {
1166        self.chain.as_ref().clone()
1167    }
1168
1169    #[getter]
1170    #[pyo3(name = "dex")]
1171    fn py_dex(&self) -> Dex {
1172        self.dex.as_ref().clone()
1173    }
1174
1175    #[getter]
1176    #[pyo3(name = "instrument_id")]
1177    fn py_instrument_id(&self) -> InstrumentId {
1178        self.instrument_id
1179    }
1180
1181    #[getter]
1182    #[pyo3(name = "pool_identifier")]
1183    fn py_pool_identifier(&self) -> String {
1184        self.pool_identifier.to_string()
1185    }
1186
1187    #[getter]
1188    #[pyo3(name = "block")]
1189    fn py_block(&self) -> u64 {
1190        self.block
1191    }
1192
1193    #[getter]
1194    #[pyo3(name = "transaction_hash")]
1195    fn py_transaction_hash(&self) -> &str {
1196        &self.transaction_hash
1197    }
1198
1199    #[getter]
1200    #[pyo3(name = "transaction_index")]
1201    fn py_transaction_index(&self) -> u32 {
1202        self.transaction_index
1203    }
1204
1205    #[getter]
1206    #[pyo3(name = "log_index")]
1207    fn py_log_index(&self) -> u32 {
1208        self.log_index
1209    }
1210
1211    #[getter]
1212    #[pyo3(name = "sender")]
1213    fn py_sender(&self) -> String {
1214        self.sender.to_string()
1215    }
1216
1217    #[getter]
1218    #[pyo3(name = "recipient")]
1219    fn py_recipient(&self) -> String {
1220        self.recipient.to_string()
1221    }
1222
1223    #[getter]
1224    #[pyo3(name = "amount0")]
1225    fn py_amount0(&self) -> String {
1226        self.amount0.to_string()
1227    }
1228
1229    #[getter]
1230    #[pyo3(name = "amount1")]
1231    fn py_amount1(&self) -> String {
1232        self.amount1.to_string()
1233    }
1234
1235    #[getter]
1236    #[pyo3(name = "paid0")]
1237    fn py_paid0(&self) -> String {
1238        self.paid0.to_string()
1239    }
1240
1241    #[getter]
1242    #[pyo3(name = "paid1")]
1243    fn py_paid1(&self) -> String {
1244        self.paid1.to_string()
1245    }
1246
1247    #[getter]
1248    #[pyo3(name = "timestamp")]
1249    fn py_timestamp(&self) -> u64 {
1250        self.ts_event.as_u64()
1251    }
1252
1253    #[getter]
1254    #[pyo3(name = "ts_event")]
1255    fn py_ts_event(&self) -> u64 {
1256        self.ts_event.as_u64()
1257    }
1258
1259    #[getter]
1260    #[pyo3(name = "ts_init")]
1261    fn py_ts_init(&self) -> u64 {
1262        self.ts_init.as_u64()
1263    }
1264}
1265
1266#[pymethods]
1267#[pyo3_stub_gen::derive::gen_stub_pymethods]
1268impl Transaction {
1269    /// Represents a transaction on an EVM based blockchain.
1270    #[new]
1271    #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
1272    fn py_new(
1273        chain: Chain,
1274        hash: String,
1275        block_hash: String,
1276        block_number: u64,
1277        from: String,
1278        to: String,
1279        gas: String,
1280        gas_price: String,
1281        transaction_index: u64,
1282        value: String,
1283    ) -> PyResult<Self> {
1284        let from = from.parse().map_err(to_pyvalue_err)?;
1285        let to = to.parse().map_err(to_pyvalue_err)?;
1286        let gas = gas.parse().map_err(to_pyvalue_err)?;
1287        let gas_price = gas_price.parse().map_err(to_pyvalue_err)?;
1288        let value = value.parse().map_err(to_pyvalue_err)?;
1289        Ok(Self::new(
1290            chain,
1291            hash,
1292            block_hash,
1293            block_number,
1294            from,
1295            to,
1296            gas,
1297            gas_price,
1298            transaction_index,
1299            value,
1300        ))
1301    }
1302
1303    fn __str__(&self) -> String {
1304        format!(
1305            "Transaction(chain={}, hash={}, block_number={}, from={}, to={}, value={})",
1306            self.chain.name, self.hash, self.block_number, self.from, self.to, self.value
1307        )
1308    }
1309
1310    fn __repr__(&self) -> String {
1311        format!("{self:?}")
1312    }
1313
1314    fn __hash__(&self) -> u64 {
1315        let mut hasher = DefaultHasher::new();
1316        self.hash.hash(&mut hasher);
1317        hasher.finish()
1318    }
1319
1320    fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
1321        match op {
1322            CompareOp::Eq => self.hash == other.hash,
1323            CompareOp::Ne => self.hash != other.hash,
1324            _ => panic!("Unsupported comparison for Transaction"),
1325        }
1326    }
1327
1328    #[getter]
1329    #[pyo3(name = "chain")]
1330    fn py_chain(&self) -> Chain {
1331        self.chain.clone()
1332    }
1333
1334    #[getter]
1335    #[pyo3(name = "hash")]
1336    fn py_hash(&self) -> &str {
1337        &self.hash
1338    }
1339
1340    #[getter]
1341    #[pyo3(name = "block_hash")]
1342    fn py_block_hash(&self) -> &str {
1343        &self.block_hash
1344    }
1345
1346    #[getter]
1347    #[pyo3(name = "block_number")]
1348    fn py_block_number(&self) -> u64 {
1349        self.block_number
1350    }
1351
1352    #[getter]
1353    #[pyo3(name = "from")]
1354    fn py_from(&self) -> String {
1355        self.from.to_string()
1356    }
1357
1358    #[getter]
1359    #[pyo3(name = "to")]
1360    fn py_to(&self) -> String {
1361        self.to.to_string()
1362    }
1363
1364    #[getter]
1365    #[pyo3(name = "value")]
1366    fn py_value(&self) -> String {
1367        self.value.to_string()
1368    }
1369
1370    #[getter]
1371    #[pyo3(name = "transaction_index")]
1372    fn py_transaction_index(&self) -> u64 {
1373        self.transaction_index
1374    }
1375
1376    #[getter]
1377    #[pyo3(name = "gas")]
1378    fn py_gas(&self) -> String {
1379        self.gas.to_string()
1380    }
1381
1382    #[getter]
1383    #[pyo3(name = "gas_price")]
1384    fn py_gas_price(&self) -> String {
1385        self.gas_price.to_string()
1386    }
1387}