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, PoolFlash, PoolLiquidityUpdate,
33            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 = "timestamp")]
366    fn py_timestamp(&self) -> u64 {
367        self.ts_event.as_u64()
368    }
369
370    #[getter]
371    #[pyo3(name = "ts_event")]
372    fn py_ts_event(&self) -> u64 {
373        self.ts_event.as_u64()
374    }
375
376    #[getter]
377    #[pyo3(name = "ts_init")]
378    fn py_ts_init(&self) -> u64 {
379        self.ts_init.as_u64()
380    }
381}
382
383#[pymethods]
384#[pyo3_stub_gen::derive::gen_stub_pymethods]
385impl PoolLiquidityUpdate {
386    /// Represents a liquidity update event in a decentralized exchange (DEX) pool.
387    #[new]
388    #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
389    fn py_new(
390        chain: Chain,
391        dex: Dex,
392        pool_identifier: String,
393        instrument_id: InstrumentId,
394        kind: PoolLiquidityUpdateType,
395        block: u64,
396        transaction_hash: String,
397        transaction_index: u32,
398        log_index: u32,
399        sender: Option<String>,
400        owner: String,
401        position_liquidity: String,
402        amount0: String,
403        amount1: String,
404        tick_lower: i32,
405        tick_upper: i32,
406        timestamp: u64,
407    ) -> PyResult<Self> {
408        let sender = sender
409            .map(|s| s.parse())
410            .transpose()
411            .map_err(to_pyvalue_err)?;
412        let owner = owner.parse().map_err(to_pyvalue_err)?;
413        let position_liquidity = position_liquidity.parse().map_err(to_pyvalue_err)?;
414        let amount0 = amount0.parse().map_err(to_pyvalue_err)?;
415        let amount1 = amount1.parse().map_err(to_pyvalue_err)?;
416        let pool_identifier = pool_identifier.parse().map_err(to_pyvalue_err)?;
417        Ok(Self::new(
418            Arc::new(chain),
419            Arc::new(dex),
420            instrument_id,
421            pool_identifier,
422            kind,
423            block,
424            transaction_hash,
425            transaction_index,
426            log_index,
427            sender,
428            owner,
429            position_liquidity,
430            amount0,
431            amount1,
432            tick_lower,
433            tick_upper,
434            timestamp.into(), // ts_event
435            timestamp.into(), // ts_init (single Python timestamp)
436        ))
437    }
438
439    fn __str__(&self) -> String {
440        self.to_string()
441    }
442
443    fn __repr__(&self) -> String {
444        format!("{self:?}")
445    }
446
447    fn __hash__(&self) -> u64 {
448        let mut hasher = DefaultHasher::new();
449        self.chain.chain_id.hash(&mut hasher);
450        self.transaction_hash.hash(&mut hasher);
451        self.log_index.hash(&mut hasher);
452        hasher.finish()
453    }
454
455    fn __richcmp__(&self, other: &Self, op: pyo3::pyclass::CompareOp) -> bool {
456        match op {
457            CompareOp::Eq => self == other,
458            CompareOp::Ne => self != other,
459            _ => panic!("Unsupported comparison for PoolLiquidityUpdate"),
460        }
461    }
462
463    #[getter]
464    #[pyo3(name = "chain")]
465    fn py_chain(&self) -> Chain {
466        self.chain.as_ref().clone()
467    }
468
469    #[getter]
470    #[pyo3(name = "dex")]
471    fn py_dex(&self) -> Dex {
472        self.dex.as_ref().clone()
473    }
474
475    #[getter]
476    #[pyo3(name = "instrument_id")]
477    fn py_instrument_id(&self) -> InstrumentId {
478        self.instrument_id
479    }
480
481    #[getter]
482    #[pyo3(name = "pool_identifier")]
483    fn py_pool_identifier(&self) -> String {
484        self.pool_identifier.to_string()
485    }
486
487    #[getter]
488    #[pyo3(name = "kind")]
489    fn py_kind(&self) -> PoolLiquidityUpdateType {
490        self.kind
491    }
492
493    #[getter]
494    #[pyo3(name = "block")]
495    fn py_block(&self) -> u64 {
496        self.block
497    }
498
499    #[getter]
500    #[pyo3(name = "transaction_hash")]
501    fn py_transaction_hash(&self) -> &str {
502        &self.transaction_hash
503    }
504
505    #[getter]
506    #[pyo3(name = "transaction_index")]
507    fn py_transaction_index(&self) -> u32 {
508        self.transaction_index
509    }
510
511    #[getter]
512    #[pyo3(name = "log_index")]
513    fn py_log_index(&self) -> u32 {
514        self.log_index
515    }
516
517    #[getter]
518    #[pyo3(name = "sender")]
519    fn py_sender(&self) -> Option<String> {
520        self.sender.map(|s| s.to_string())
521    }
522
523    #[getter]
524    #[pyo3(name = "owner")]
525    fn py_owner(&self) -> String {
526        self.owner.to_string()
527    }
528
529    #[getter]
530    #[pyo3(name = "position_liquidity")]
531    fn py_position_liquidity(&self) -> String {
532        self.position_liquidity.to_string()
533    }
534
535    #[getter]
536    #[pyo3(name = "amount0")]
537    fn py_amount0(&self) -> String {
538        self.amount0.to_string()
539    }
540
541    #[getter]
542    #[pyo3(name = "amount1")]
543    fn py_amount1(&self) -> String {
544        self.amount1.to_string()
545    }
546
547    #[getter]
548    #[pyo3(name = "tick_lower")]
549    fn py_tick_lower(&self) -> i32 {
550        self.tick_lower
551    }
552
553    #[getter]
554    #[pyo3(name = "tick_upper")]
555    fn py_tick_upper(&self) -> i32 {
556        self.tick_upper
557    }
558
559    #[getter]
560    #[pyo3(name = "timestamp")]
561    fn py_timestamp(&self) -> u64 {
562        self.ts_event.as_u64()
563    }
564
565    #[getter]
566    #[pyo3(name = "ts_event")]
567    fn py_ts_event(&self) -> u64 {
568        self.ts_event.as_u64()
569    }
570
571    #[getter]
572    #[pyo3(name = "ts_init")]
573    fn py_ts_init(&self) -> u64 {
574        self.ts_init.as_u64()
575    }
576}
577
578#[pymethods]
579#[pyo3_stub_gen::derive::gen_stub_pymethods]
580impl PoolFeeCollect {
581    /// Represents a fee collection event in a decentralized exchange (DEX) pool.
582    #[new]
583    #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
584    fn py_new(
585        chain: Chain,
586        dex: Dex,
587        pool_identifier: String,
588        instrument_id: InstrumentId,
589        block: u64,
590        transaction_hash: String,
591        transaction_index: u32,
592        log_index: u32,
593        owner: String,
594        amount0: String,
595        amount1: String,
596        tick_lower: i32,
597        tick_upper: i32,
598        timestamp: u64,
599    ) -> PyResult<Self> {
600        let owner = owner.parse().map_err(to_pyvalue_err)?;
601        let amount0 = amount0.parse().map_err(to_pyvalue_err)?;
602        let amount1 = amount1.parse().map_err(to_pyvalue_err)?;
603        let pool_identifier = pool_identifier.parse().map_err(to_pyvalue_err)?;
604        Ok(Self::new(
605            Arc::new(chain),
606            Arc::new(dex),
607            instrument_id,
608            pool_identifier,
609            block,
610            transaction_hash,
611            transaction_index,
612            log_index,
613            owner,
614            amount0,
615            amount1,
616            tick_lower,
617            tick_upper,
618            timestamp.into(), // ts_event
619            timestamp.into(), // ts_init (single Python timestamp)
620        ))
621    }
622
623    fn __str__(&self) -> String {
624        self.to_string()
625    }
626
627    fn __repr__(&self) -> String {
628        format!("{self:?}")
629    }
630
631    fn __hash__(&self) -> u64 {
632        let mut hasher = DefaultHasher::new();
633        self.chain.chain_id.hash(&mut hasher);
634        self.transaction_hash.hash(&mut hasher);
635        self.log_index.hash(&mut hasher);
636        hasher.finish()
637    }
638
639    fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
640        match op {
641            CompareOp::Eq => self == other,
642            CompareOp::Ne => self != other,
643            _ => panic!("Unsupported comparison for PoolFeeCollect"),
644        }
645    }
646
647    #[getter]
648    #[pyo3(name = "chain")]
649    fn py_chain(&self) -> Chain {
650        self.chain.as_ref().clone()
651    }
652
653    #[getter]
654    #[pyo3(name = "dex")]
655    fn py_dex(&self) -> Dex {
656        self.dex.as_ref().clone()
657    }
658
659    #[getter]
660    #[pyo3(name = "instrument_id")]
661    fn py_instrument_id(&self) -> InstrumentId {
662        self.instrument_id
663    }
664
665    #[getter]
666    #[pyo3(name = "pool_identifier")]
667    fn py_pool_identifier(&self) -> String {
668        self.pool_identifier.to_string()
669    }
670
671    #[getter]
672    #[pyo3(name = "block")]
673    fn py_block(&self) -> u64 {
674        self.block
675    }
676
677    #[getter]
678    #[pyo3(name = "transaction_hash")]
679    fn py_transaction_hash(&self) -> &str {
680        &self.transaction_hash
681    }
682
683    #[getter]
684    #[pyo3(name = "transaction_index")]
685    fn py_transaction_index(&self) -> u32 {
686        self.transaction_index
687    }
688
689    #[getter]
690    #[pyo3(name = "log_index")]
691    fn py_log_index(&self) -> u32 {
692        self.log_index
693    }
694
695    #[getter]
696    #[pyo3(name = "owner")]
697    fn py_owner(&self) -> String {
698        self.owner.to_string()
699    }
700
701    #[getter]
702    #[pyo3(name = "amount0")]
703    fn py_amount0(&self) -> String {
704        self.amount0.to_string()
705    }
706
707    #[getter]
708    #[pyo3(name = "amount1")]
709    fn py_amount1(&self) -> String {
710        self.amount1.to_string()
711    }
712
713    #[getter]
714    #[pyo3(name = "tick_lower")]
715    fn py_tick_lower(&self) -> i32 {
716        self.tick_lower
717    }
718
719    #[getter]
720    #[pyo3(name = "tick_upper")]
721    fn py_tick_upper(&self) -> i32 {
722        self.tick_upper
723    }
724
725    #[getter]
726    #[pyo3(name = "timestamp")]
727    fn py_timestamp(&self) -> u64 {
728        self.ts_event.as_u64()
729    }
730
731    #[getter]
732    #[pyo3(name = "ts_event")]
733    fn py_ts_event(&self) -> u64 {
734        self.ts_event.as_u64()
735    }
736
737    #[getter]
738    #[pyo3(name = "ts_init")]
739    fn py_ts_init(&self) -> u64 {
740        self.ts_init.as_u64()
741    }
742}
743
744#[pymethods]
745#[pyo3_stub_gen::derive::gen_stub_pymethods]
746impl PoolFlash {
747    /// Represents a flash loan event from a Uniswap V3 pool.
748    ///
749    /// Flash loans allow users to borrow tokens without collateral as long as they are returned
750    /// within the same transaction. Fees are paid on the borrowed amount, which are added to
751    /// the pool's fee growth accumulators.
752    #[new]
753    #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
754    fn py_new(
755        chain: Chain,
756        dex: Dex,
757        pool_identifier: String,
758        instrument_id: InstrumentId,
759        block: u64,
760        transaction_hash: String,
761        transaction_index: u32,
762        log_index: u32,
763        sender: String,
764        recipient: String,
765        amount0: String,
766        amount1: String,
767        paid0: String,
768        paid1: String,
769        timestamp: u64,
770    ) -> PyResult<Self> {
771        let sender = sender.parse().map_err(to_pyvalue_err)?;
772        let recipient = recipient.parse().map_err(to_pyvalue_err)?;
773        let amount0 = amount0.parse().map_err(to_pyvalue_err)?;
774        let amount1 = amount1.parse().map_err(to_pyvalue_err)?;
775        let paid0 = paid0.parse().map_err(to_pyvalue_err)?;
776        let paid1 = paid1.parse().map_err(to_pyvalue_err)?;
777        let pool_identifier = pool_identifier.parse().map_err(to_pyvalue_err)?;
778        Ok(Self::new(
779            Arc::new(chain),
780            Arc::new(dex),
781            instrument_id,
782            pool_identifier,
783            block,
784            transaction_hash,
785            transaction_index,
786            log_index,
787            timestamp.into(), // ts_event
788            timestamp.into(), // ts_init (single Python timestamp)
789            sender,
790            recipient,
791            amount0,
792            amount1,
793            paid0,
794            paid1,
795        ))
796    }
797
798    fn __str__(&self) -> String {
799        self.to_string()
800    }
801
802    fn __repr__(&self) -> String {
803        format!("{self:?}")
804    }
805
806    fn __hash__(&self) -> u64 {
807        let mut hasher = DefaultHasher::new();
808        self.chain.chain_id.hash(&mut hasher);
809        self.transaction_hash.hash(&mut hasher);
810        self.log_index.hash(&mut hasher);
811        hasher.finish()
812    }
813
814    fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
815        match op {
816            CompareOp::Eq => self == other,
817            CompareOp::Ne => self != other,
818            _ => panic!("Unsupported comparison for PoolFlash"),
819        }
820    }
821
822    #[getter]
823    #[pyo3(name = "chain")]
824    fn py_chain(&self) -> Chain {
825        self.chain.as_ref().clone()
826    }
827
828    #[getter]
829    #[pyo3(name = "dex")]
830    fn py_dex(&self) -> Dex {
831        self.dex.as_ref().clone()
832    }
833
834    #[getter]
835    #[pyo3(name = "instrument_id")]
836    fn py_instrument_id(&self) -> InstrumentId {
837        self.instrument_id
838    }
839
840    #[getter]
841    #[pyo3(name = "pool_identifier")]
842    fn py_pool_identifier(&self) -> String {
843        self.pool_identifier.to_string()
844    }
845
846    #[getter]
847    #[pyo3(name = "block")]
848    fn py_block(&self) -> u64 {
849        self.block
850    }
851
852    #[getter]
853    #[pyo3(name = "transaction_hash")]
854    fn py_transaction_hash(&self) -> &str {
855        &self.transaction_hash
856    }
857
858    #[getter]
859    #[pyo3(name = "transaction_index")]
860    fn py_transaction_index(&self) -> u32 {
861        self.transaction_index
862    }
863
864    #[getter]
865    #[pyo3(name = "log_index")]
866    fn py_log_index(&self) -> u32 {
867        self.log_index
868    }
869
870    #[getter]
871    #[pyo3(name = "sender")]
872    fn py_sender(&self) -> String {
873        self.sender.to_string()
874    }
875
876    #[getter]
877    #[pyo3(name = "recipient")]
878    fn py_recipient(&self) -> String {
879        self.recipient.to_string()
880    }
881
882    #[getter]
883    #[pyo3(name = "amount0")]
884    fn py_amount0(&self) -> String {
885        self.amount0.to_string()
886    }
887
888    #[getter]
889    #[pyo3(name = "amount1")]
890    fn py_amount1(&self) -> String {
891        self.amount1.to_string()
892    }
893
894    #[getter]
895    #[pyo3(name = "paid0")]
896    fn py_paid0(&self) -> String {
897        self.paid0.to_string()
898    }
899
900    #[getter]
901    #[pyo3(name = "paid1")]
902    fn py_paid1(&self) -> String {
903        self.paid1.to_string()
904    }
905
906    #[getter]
907    #[pyo3(name = "timestamp")]
908    fn py_timestamp(&self) -> u64 {
909        self.ts_event.as_u64()
910    }
911
912    #[getter]
913    #[pyo3(name = "ts_event")]
914    fn py_ts_event(&self) -> u64 {
915        self.ts_event.as_u64()
916    }
917
918    #[getter]
919    #[pyo3(name = "ts_init")]
920    fn py_ts_init(&self) -> u64 {
921        self.ts_init.as_u64()
922    }
923}
924
925#[pymethods]
926#[pyo3_stub_gen::derive::gen_stub_pymethods]
927impl Transaction {
928    /// Represents a transaction on an EVM based blockchain.
929    #[new]
930    #[expect(clippy::too_many_arguments, clippy::needless_pass_by_value)]
931    fn py_new(
932        chain: Chain,
933        hash: String,
934        block_hash: String,
935        block_number: u64,
936        from: String,
937        to: String,
938        gas: String,
939        gas_price: String,
940        transaction_index: u64,
941        value: String,
942    ) -> PyResult<Self> {
943        let from = from.parse().map_err(to_pyvalue_err)?;
944        let to = to.parse().map_err(to_pyvalue_err)?;
945        let gas = gas.parse().map_err(to_pyvalue_err)?;
946        let gas_price = gas_price.parse().map_err(to_pyvalue_err)?;
947        let value = value.parse().map_err(to_pyvalue_err)?;
948        Ok(Self::new(
949            chain,
950            hash,
951            block_hash,
952            block_number,
953            from,
954            to,
955            gas,
956            gas_price,
957            transaction_index,
958            value,
959        ))
960    }
961
962    fn __str__(&self) -> String {
963        format!(
964            "Transaction(chain={}, hash={}, block_number={}, from={}, to={}, value={})",
965            self.chain.name, self.hash, self.block_number, self.from, self.to, self.value
966        )
967    }
968
969    fn __repr__(&self) -> String {
970        format!("{self:?}")
971    }
972
973    fn __hash__(&self) -> u64 {
974        let mut hasher = DefaultHasher::new();
975        self.hash.hash(&mut hasher);
976        hasher.finish()
977    }
978
979    fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
980        match op {
981            CompareOp::Eq => self.hash == other.hash,
982            CompareOp::Ne => self.hash != other.hash,
983            _ => panic!("Unsupported comparison for Transaction"),
984        }
985    }
986
987    #[getter]
988    #[pyo3(name = "chain")]
989    fn py_chain(&self) -> Chain {
990        self.chain.clone()
991    }
992
993    #[getter]
994    #[pyo3(name = "hash")]
995    fn py_hash(&self) -> &str {
996        &self.hash
997    }
998
999    #[getter]
1000    #[pyo3(name = "block_hash")]
1001    fn py_block_hash(&self) -> &str {
1002        &self.block_hash
1003    }
1004
1005    #[getter]
1006    #[pyo3(name = "block_number")]
1007    fn py_block_number(&self) -> u64 {
1008        self.block_number
1009    }
1010
1011    #[getter]
1012    #[pyo3(name = "from")]
1013    fn py_from(&self) -> String {
1014        self.from.to_string()
1015    }
1016
1017    #[getter]
1018    #[pyo3(name = "to")]
1019    fn py_to(&self) -> String {
1020        self.to.to_string()
1021    }
1022
1023    #[getter]
1024    #[pyo3(name = "value")]
1025    fn py_value(&self) -> String {
1026        self.value.to_string()
1027    }
1028
1029    #[getter]
1030    #[pyo3(name = "transaction_index")]
1031    fn py_transaction_index(&self) -> u64 {
1032        self.transaction_index
1033    }
1034
1035    #[getter]
1036    #[pyo3(name = "gas")]
1037    fn py_gas(&self) -> String {
1038        self.gas.to_string()
1039    }
1040
1041    #[getter]
1042    #[pyo3(name = "gas_price")]
1043    fn py_gas_price(&self) -> String {
1044        self.gas_price.to_string()
1045    }
1046}