Skip to main content

chaincodec_core/
event.rs

1//! Raw and decoded event types.
2
3use crate::chain::ChainId;
4use crate::types::NormalizedValue;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// A raw, undecoded event as received from an RPC node or batch loader.
9/// This is the input to every decoder.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct RawEvent {
12    /// The chain this event came from
13    pub chain: ChainId,
14    /// Transaction hash (hex-encoded with 0x prefix for EVM, base58 for Solana, etc.)
15    pub tx_hash: String,
16    /// Block number / slot / height
17    pub block_number: u64,
18    /// Block timestamp (Unix seconds, UTC)
19    pub block_timestamp: i64,
20    /// Log / event index within the transaction
21    pub log_index: u32,
22    /// EVM: topics[0] is the event signature hash; additional topics are indexed params.
23    /// Solana: discriminator bytes. Cosmos: event type string.
24    pub topics: Vec<String>,
25    /// ABI-encoded non-indexed parameters (EVM) or raw instruction data (Solana/Cosmos).
26    pub data: Vec<u8>,
27    /// Contract address that emitted the event
28    pub address: String,
29    /// Optional raw transaction receipt for additional context
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub raw_receipt: Option<serde_json::Value>,
32}
33
34impl RawEvent {
35    /// EVM convenience: returns topics[0] as the event signature fingerprint, if present.
36    pub fn evm_event_signature(&self) -> Option<&str> {
37        self.topics.first().map(|s| s.as_str())
38    }
39}
40
41/// The keccak256 (EVM) or SHA-256 (non-EVM) hash of an event's canonical signature.
42/// Used for O(1) schema lookup.
43#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
44pub struct EventFingerprint(pub String);
45
46impl EventFingerprint {
47    pub fn new(hex: impl Into<String>) -> Self {
48        Self(hex.into())
49    }
50
51    pub fn as_hex(&self) -> &str {
52        &self.0
53    }
54}
55
56impl std::fmt::Display for EventFingerprint {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        write!(f, "{}", self.0)
59    }
60}
61
62/// A fully decoded event — the primary output of ChainCodec.
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct DecodedEvent {
65    /// The chain this event came from
66    pub chain: ChainId,
67    /// Matched schema name, e.g. "UniswapV3Swap"
68    pub schema: String,
69    /// Schema version that was used to decode this event
70    pub schema_version: u32,
71    /// Transaction hash
72    pub tx_hash: String,
73    /// Block number
74    pub block_number: u64,
75    /// Block timestamp (Unix seconds)
76    pub block_timestamp: i64,
77    /// Log index
78    pub log_index: u32,
79    /// Contract address
80    pub address: String,
81    /// Decoded, normalized field values keyed by field name
82    pub fields: HashMap<String, NormalizedValue>,
83    /// The fingerprint used to match this event
84    pub fingerprint: EventFingerprint,
85    /// Optional: fields that failed to decode (only present in lenient mode)
86    #[serde(skip_serializing_if = "HashMap::is_empty", default)]
87    pub decode_errors: HashMap<String, String>,
88}
89
90impl DecodedEvent {
91    /// Get a field value by name.
92    pub fn field(&self, name: &str) -> Option<&NormalizedValue> {
93        self.fields.get(name)
94    }
95
96    /// Returns `true` if any fields failed to decode.
97    pub fn has_errors(&self) -> bool {
98        !self.decode_errors.is_empty()
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use crate::chain::chains;
106
107    fn sample_raw() -> RawEvent {
108        RawEvent {
109            chain: chains::ethereum(),
110            tx_hash: "0xabc123".into(),
111            block_number: 19_000_000,
112            block_timestamp: 1_700_000_000,
113            log_index: 2,
114            topics: vec![
115                "0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67".into(),
116            ],
117            data: vec![0u8; 32],
118            address: "0xe0554a476a092703abdb3ef35c80e0d76d32939f".into(),
119            raw_receipt: None,
120        }
121    }
122
123    #[test]
124    fn raw_event_evm_signature() {
125        let e = sample_raw();
126        assert!(e.evm_event_signature().unwrap().starts_with("0xc42079"));
127    }
128}