sui_jsonrpc/msgs/
sui_event.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::fmt;
5use std::fmt::Display;
6
7use af_sui_types::{
8    Address as SuiAddress,
9    Identifier,
10    ObjectId,
11    StructTag,
12    TransactionDigest,
13    encode_base64_default,
14};
15use json_to_table::json_to_table;
16use serde::{Deserialize, Serialize};
17use serde_json::{Value, json};
18use serde_with::{DisplayFromStr, IfIsHumanReadable, serde_as};
19use tabled::settings::Style as TableStyle;
20
21use super::Page;
22use crate::serde::{Base64orBase58, BigInt};
23
24pub type EventPage = Page<SuiEvent, EventID>;
25
26#[serde_as]
27#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
28#[serde(rename = "Event", rename_all = "camelCase")]
29pub struct SuiEvent {
30    /// Sequential event ID, ie (transaction seq number, event seq number).
31    /// 1) Serves as a unique event ID for each fullnode
32    /// 2) Also serves to sequence events for the purposes of pagination and querying.
33    ///    A higher id is an event seen later by that fullnode.
34    /// This ID is the "cursor" for event querying.
35    pub id: EventID,
36    /// Move package where this event was emitted.
37    pub package_id: ObjectId,
38    /// Move module where this event was emitted.
39    #[serde_as(as = "DisplayFromStr")]
40    pub transaction_module: Identifier,
41    /// Sender's Sui address.
42    pub sender: SuiAddress,
43    /// Move event type.
44    // #[serde_as(as = "SuiStructTag")]
45    #[serde_as(as = "DisplayFromStr")]
46    pub type_: StructTag,
47    /// Parsed json value of the event
48    pub parsed_json: Value,
49    /// Base64 encoded bcs bytes of the move event
50    #[serde_as(as = "Base64orBase58")]
51    pub bcs: Vec<u8>,
52    /// UTC timestamp in milliseconds since epoch (1/1/1970)
53    #[serde(skip_serializing_if = "Option::is_none")]
54    #[serde_as(as = "Option<BigInt<u64>>")]
55    pub timestamp_ms: Option<u64>,
56}
57
58impl Display for SuiEvent {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        let parsed_json = &mut self.parsed_json.clone();
61        bytes_array_to_base64(parsed_json);
62        let mut table = json_to_table(parsed_json);
63        let style = TableStyle::modern();
64        table.collapse().with(style);
65        write!(
66            f,
67            " ┌──\n │ EventID: {}:{}\n │ PackageID: {}\n │ Transaction Module: {}\n │ Sender: {}\n │ EventType: {}\n",
68            self.id.tx_digest,
69            self.id.event_seq,
70            self.package_id,
71            self.transaction_module,
72            self.sender,
73            self.type_
74        )?;
75        if let Some(ts) = self.timestamp_ms {
76            writeln!(f, " │ Timestamp: {}\n └──", ts)?;
77        }
78        writeln!(f, " │ ParsedJSON:")?;
79        let table_string = table.to_string();
80        let table_rows = table_string.split_inclusive('\n');
81        for r in table_rows {
82            write!(f, " │   {r}")?;
83        }
84
85        write!(f, "\n └──")
86    }
87}
88
89/// Convert a json array of bytes to Base64
90fn bytes_array_to_base64(v: &mut Value) {
91    match v {
92        Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => (),
93        Value::Array(vals) => {
94            if let Some(vals) = vals.iter().map(try_into_byte).collect::<Option<Vec<_>>>() {
95                *v = json!(encode_base64_default(vals))
96            } else {
97                for val in vals {
98                    bytes_array_to_base64(val)
99                }
100            }
101        }
102        Value::Object(map) => {
103            for val in map.values_mut() {
104                bytes_array_to_base64(val)
105            }
106        }
107    }
108}
109
110/// Try to convert a json Value object into an u8.
111fn try_into_byte(v: &Value) -> Option<u8> {
112    let num = v.as_u64()?;
113    (num <= 255).then_some(num as u8)
114}
115
116/// Unique ID of a Sui Event, the ID is a combination of tx seq number and event seq number,
117/// the ID is local to the particular fullnode and will be different from other fullnode.
118#[serde_as]
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
120#[serde(rename_all = "camelCase")]
121pub struct EventID {
122    pub tx_digest: TransactionDigest,
123    #[serde_as(as = "IfIsHumanReadable<BigInt<u64>, _>")]
124    pub event_seq: u64,
125}
126
127#[serde_as]
128#[derive(Clone, Debug, Serialize, Deserialize)]
129pub enum EventFilter {
130    /// Return all events.
131    All([Box<EventFilter>; 0]),
132    /// Query by sender address.
133    Sender(SuiAddress),
134    /// Return events emitted by the given transaction.
135    Transaction(
136        ///digest of the transaction, as base-64 encoded string
137        TransactionDigest,
138    ),
139    /// Return events emitted in a specified Move module.
140    /// If the event is defined in Module A but emitted in a tx with Module B,
141    /// query `MoveModule` by module B returns the event.
142    /// Query `MoveEventModule` by module A returns the event too.
143    MoveModule {
144        /// the Move package ID
145        package: ObjectId,
146        /// the module name
147        #[serde_as(as = "DisplayFromStr")]
148        module: Identifier,
149    },
150    /// Return events with the given Move event struct name (struct tag).
151    /// For example, if the event is defined in `0xabcd::MyModule`, and named
152    /// `Foo`, then the struct tag is `0xabcd::MyModule::Foo`.
153    MoveEventType(#[serde_as(as = "DisplayFromStr")] StructTag),
154    /// Return events with the given Move module name where the event struct is defined.
155    /// If the event is defined in Module A but emitted in a tx with Module B,
156    /// query `MoveEventModule` by module A returns the event.
157    /// Query `MoveModule` by module B returns the event too.
158    MoveEventModule {
159        /// the Move package ID
160        package: ObjectId,
161        /// the module name
162        #[serde_as(as = "DisplayFromStr")]
163        module: Identifier,
164    },
165    /// Return events emitted in [start_time, end_time] interval
166    #[serde(rename_all = "camelCase")]
167    TimeRange {
168        /// left endpoint of time interval, milliseconds since epoch, inclusive
169        #[serde_as(as = "BigInt<u64>")]
170        start_time: u64,
171        /// right endpoint of time interval, milliseconds since epoch, exclusive
172        #[serde_as(as = "BigInt<u64>")]
173        end_time: u64,
174    },
175}
176
177#[cfg(test)]
178mod test {
179    use super::*;
180
181    const NEW_EVENT_JSON: &str = r#"{
182    "id": {
183        "txDigest": "BwwTktCxZryxsRdQ8JqFNYKYZDLDCQ3L59LCtQzgJgEo",
184        "eventSeq": "0"
185    },
186    "packageId": "0x0000000000000000000000000000000000000000000000000000000000000003",
187    "transactionModule": "sui_system",
188    "sender": "0x0000000000000000000000000000000000000000000000000000000000000000",
189    "type": "0x3::validator::StakingRequestEvent",
190    "parsedJson": {
191        "amount": "3680004485920",
192        "epoch": "0",
193        "pool_id": "0x568e13ac056b900ee3ba2f7c85f0c62e19cd25a14ea6f064c3799870ff7d0a9a",
194        "staker_address": "0x44b1b319e23495995fc837dafd28fc6af8b645edddff0fc1467f1ad631362c23",
195        "validator_address": "0x44b1b319e23495995fc837dafd28fc6af8b645edddff0fc1467f1ad631362c23"
196    },
197    "bcsEncoding": "base64",
198    "bcs": "Vo4TrAVrkA7jui98hfDGLhnNJaFOpvBkw3mYcP99CppEsbMZ4jSVmV/IN9r9KPxq+LZF7d3/D8FGfxrWMTYsI0SxsxniNJWZX8g32v0o/Gr4tkXt3f8PwUZ/GtYxNiwjAAAAAAAAAAAgM1zRWAMAAA==",
199    "timestampMs": "1689867116721"
200      }"#;
201
202    #[test]
203    fn new_bcs_format() {
204        serde_json::from_str::<SuiEvent>(NEW_EVENT_JSON).unwrap();
205    }
206}