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