1use std::{fmt, fmt::Display, str::FromStr};
6
7use fastcrypto::encoding::{Base58, Base64};
8use iota_sdk_types::{Address, Event, Identifier, ObjectId, StructTag, TransactionDigest};
9use iota_types::{
10 error::IotaResult,
11 event::{EventEnvelope, EventID},
12 object::bounded_visitor::BoundedVisitor,
13};
14use json_to_table::json_to_table;
15use move_core_types::annotated_value::MoveDatatypeLayout;
16use schemars::JsonSchema;
17use serde::{Deserialize, Serialize};
18use serde_json::{Value, json};
19use serde_with::{DisplayFromStr, serde_as};
20use tabled::settings::Style as TableStyle;
21
22use crate::{
23 Page,
24 iota_primitives::{
25 Address as AddressSchema, Base58 as Base58Schema, Base64 as Base64Schema,
26 Identifier as IdentifierSchema, ObjectId as ObjectIdSchema, StructTag as StructTagSchema,
27 },
28 type_and_fields_from_move_event_data,
29};
30
31pub type EventPage = Page<IotaEvent, EventID>;
32
33#[serde_as]
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)]
37#[serde(rename_all = "camelCase")]
38#[schemars(rename = "EventID")]
39pub struct IotaEventID {
40 #[serde_as(as = "Base58Schema")]
41 #[schemars(with = "Base58Schema")]
42 pub tx_digest: TransactionDigest,
43 #[schemars(with = "String")]
44 #[serde_as(as = "DisplayFromStr")]
45 pub event_seq: u64,
46}
47
48impl From<EventID> for IotaEventID {
49 fn from(id: EventID) -> Self {
50 Self {
51 tx_digest: id.tx_digest,
52 event_seq: id.event_seq,
53 }
54 }
55}
56
57impl From<IotaEventID> for EventID {
58 fn from(id: IotaEventID) -> Self {
59 Self {
60 tx_digest: id.tx_digest,
61 event_seq: id.event_seq,
62 }
63 }
64}
65
66#[serde_as]
67#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
68#[serde(rename = "Event", rename_all = "camelCase")]
69pub struct IotaEvent {
70 #[schemars(with = "IotaEventID")]
76 pub id: EventID,
77 #[serde_as(as = "ObjectIdSchema")]
79 #[schemars(with = "ObjectIdSchema")]
80 pub package_id: ObjectId,
81 #[serde_as(as = "IdentifierSchema")]
82 #[schemars(with = "IdentifierSchema")]
83 pub transaction_module: Identifier,
85 #[serde_as(as = "AddressSchema")]
87 #[schemars(with = "AddressSchema")]
88 pub sender: Address,
89 #[serde(rename = "type")]
91 #[schemars(with = "StructTagSchema")]
92 #[serde_as(as = "StructTagSchema")]
93 pub struct_tag: StructTag,
94 pub parsed_json: Value,
96 #[serde(flatten)]
98 pub bcs: BcsEvent,
99 #[serde(skip_serializing_if = "Option::is_none")]
101 #[schemars(with = "Option<String>")]
102 #[serde_as(as = "Option<DisplayFromStr>")]
103 pub timestamp_ms: Option<u64>,
104}
105
106#[serde_as]
107#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, JsonSchema)]
108#[serde(rename_all = "camelCase", tag = "bcsEncoding")]
109#[serde(from = "MaybeTaggedBcsEvent")]
110pub enum BcsEvent {
111 Base64 {
112 #[serde_as(as = "Base64")]
113 #[schemars(with = "Base64Schema")]
114 bcs: Vec<u8>,
115 },
116 Base58 {
117 #[serde_as(as = "Base58")]
118 #[schemars(with = "Base58Schema")]
119 bcs: Vec<u8>,
120 },
121}
122
123impl BcsEvent {
124 pub fn new(bytes: Vec<u8>) -> Self {
125 Self::Base64 { bcs: bytes }
126 }
127
128 pub fn bytes(&self) -> &[u8] {
129 match self {
130 BcsEvent::Base64 { bcs } => bcs.as_ref(),
131 BcsEvent::Base58 { bcs } => bcs.as_ref(),
132 }
133 }
134
135 pub fn into_bytes(self) -> Vec<u8> {
136 match self {
137 BcsEvent::Base64 { bcs } => bcs,
138 BcsEvent::Base58 { bcs } => bcs,
139 }
140 }
141}
142
143#[allow(unused)]
144#[serde_as]
145#[derive(Serialize, Deserialize)]
146#[serde(rename_all = "camelCase", untagged)]
147enum MaybeTaggedBcsEvent {
148 Tagged(TaggedBcsEvent),
149 Base58 {
150 #[serde_as(as = "Base58")]
151 bcs: Vec<u8>,
152 },
153}
154
155#[serde_as]
156#[derive(Serialize, Deserialize)]
157#[serde(rename_all = "camelCase", tag = "bcsEncoding")]
158enum TaggedBcsEvent {
159 Base64 {
160 #[serde_as(as = "Base64")]
161 bcs: Vec<u8>,
162 },
163 Base58 {
164 #[serde_as(as = "Base58")]
165 bcs: Vec<u8>,
166 },
167}
168
169impl From<MaybeTaggedBcsEvent> for BcsEvent {
170 fn from(event: MaybeTaggedBcsEvent) -> BcsEvent {
171 let bcs = match event {
172 MaybeTaggedBcsEvent::Tagged(TaggedBcsEvent::Base58 { bcs })
173 | MaybeTaggedBcsEvent::Base58 { bcs } => bcs,
174 MaybeTaggedBcsEvent::Tagged(TaggedBcsEvent::Base64 { bcs }) => bcs,
175 };
176
177 Self::Base64 { bcs }
180 }
181}
182
183impl From<EventEnvelope> for IotaEvent {
184 fn from(ev: EventEnvelope) -> Self {
185 Self {
186 id: EventID {
187 tx_digest: ev.tx_digest,
188 event_seq: ev.event_num,
189 },
190 package_id: ev.event.package_id,
191 transaction_module: ev.event.module,
192 sender: ev.event.sender,
193 struct_tag: ev.event.struct_tag,
194 parsed_json: ev.parsed_json,
195 bcs: BcsEvent::Base64 {
196 bcs: ev.event.contents,
197 },
198 timestamp_ms: Some(ev.timestamp),
199 }
200 }
201}
202
203impl From<IotaEvent> for Event {
204 fn from(val: IotaEvent) -> Self {
205 Event {
206 package_id: val.package_id,
207 module: val.transaction_module,
208 sender: val.sender,
209 struct_tag: val.struct_tag,
210 contents: val.bcs.into_bytes(),
211 }
212 }
213}
214
215impl IotaEvent {
216 pub fn try_from(
217 event: Event,
218 tx_digest: TransactionDigest,
219 event_seq: u64,
220 timestamp_ms: Option<u64>,
221 layout: MoveDatatypeLayout,
222 ) -> IotaResult<Self> {
223 let Event {
224 package_id,
225 module,
226 sender,
227 struct_tag: _,
228 contents,
229 } = event;
230
231 let bcs = BcsEvent::Base64 {
232 bcs: contents.to_vec(),
233 };
234
235 let move_value = BoundedVisitor::deserialize_value(&contents, &layout.into_layout())
236 .map_err(|e| iota_types::error::IotaError::ObjectDeserialization {
237 error: e.to_string(),
238 })?;
239 let (tag, fields) = type_and_fields_from_move_event_data(move_value)?;
240
241 Ok(IotaEvent {
242 id: EventID {
243 tx_digest,
244 event_seq,
245 },
246 package_id,
247 transaction_module: module,
248 sender,
249 struct_tag: tag,
250 parsed_json: fields,
251 bcs,
252 timestamp_ms,
253 })
254 }
255}
256
257impl Display for IotaEvent {
258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259 let parsed_json = &mut self.parsed_json.clone();
260 bytes_array_to_base64(parsed_json);
261 let mut table = json_to_table(parsed_json);
262 let style = TableStyle::modern();
263 table.collapse().with(style);
264 write!(
265 f,
266 " ┌──\n │ EventID: {}:{}\n │ PackageID: {}\n │ Transaction Module: {}\n │ Sender: {}\n │ EventType: {}\n",
267 self.id.tx_digest,
268 self.id.event_seq,
269 self.package_id,
270 self.transaction_module,
271 self.sender,
272 self.struct_tag
273 )?;
274 if let Some(ts) = self.timestamp_ms {
275 writeln!(f, " │ Timestamp: {ts}\n └──")?;
276 }
277 writeln!(f, " │ ParsedJSON:")?;
278 let table_string = table.to_string();
279 let table_rows = table_string.split_inclusive('\n');
280 for r in table_rows {
281 write!(f, " │ {r}")?;
282 }
283
284 write!(f, "\n └──")
285 }
286}
287
288impl IotaEvent {
289 pub fn random_for_testing() -> Self {
290 Self {
291 id: EventID {
292 tx_digest: TransactionDigest::random(),
293 event_seq: 0,
294 },
295 package_id: ObjectId::random(),
296 transaction_module: Identifier::from_str("random_for_testing").unwrap(),
297 sender: Address::random(),
298 struct_tag: StructTag::from_str("0x6666::random_for_testing::RandomForTesting")
299 .unwrap(),
300 parsed_json: json!({}),
301 bcs: BcsEvent::new(vec![]),
302 timestamp_ms: None,
303 }
304 }
305}
306
307fn bytes_array_to_base64(v: &mut Value) {
309 match v {
310 Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => (),
311 Value::Array(vals) => {
312 if let Some(vals) = vals.iter().map(try_into_byte).collect::<Option<Vec<_>>>() {
313 *v = json!(Base64::from_bytes(&vals).encoded())
314 } else {
315 for val in vals {
316 bytes_array_to_base64(val)
317 }
318 }
319 }
320 Value::Object(map) => {
321 for val in map.values_mut() {
322 bytes_array_to_base64(val)
323 }
324 }
325 }
326}
327
328fn try_into_byte(v: &Value) -> Option<u8> {
330 let num = v.as_u64()?;
331 (num <= 255).then_some(num as u8)
332}
333
334#[serde_as]
335#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
336pub enum EventFilter {
337 Sender(
339 #[serde_as(as = "AddressSchema")]
340 #[schemars(with = "AddressSchema")]
341 Address,
342 ),
343 Transaction(
345 #[serde_as(as = "Base58Schema")]
347 #[schemars(with = "Base58Schema")]
348 TransactionDigest,
349 ),
350 Package(
352 #[serde_as(as = "ObjectIdSchema")]
353 #[schemars(with = "ObjectIdSchema")]
354 ObjectId,
355 ),
356 MoveModule {
361 #[serde_as(as = "ObjectIdSchema")]
363 #[schemars(with = "ObjectIdSchema")]
364 package: ObjectId,
365 #[serde_as(as = "IdentifierSchema")]
367 #[schemars(with = "IdentifierSchema")]
368 module: Identifier,
369 },
370 MoveEventType(
374 #[schemars(with = "StructTagSchema")]
375 #[serde_as(as = "StructTagSchema")]
376 StructTag,
377 ),
378 MoveEventModule {
383 #[serde_as(as = "ObjectIdSchema")]
385 #[schemars(with = "ObjectIdSchema")]
386 package: ObjectId,
387 #[serde_as(as = "IdentifierSchema")]
389 #[schemars(with = "IdentifierSchema")]
390 module: Identifier,
391 },
392 MoveEventField {
393 path: String,
394 value: Value,
395 },
396 #[serde(rename_all = "camelCase")]
398 TimeRange {
399 #[serde_as(as = "DisplayFromStr")]
401 #[schemars(with = "String")]
402 start_time: u64,
403 #[serde_as(as = "DisplayFromStr")]
405 #[schemars(with = "String")]
406 end_time: u64,
407 },
408
409 All(Vec<EventFilter>),
410 Any(Vec<EventFilter>),
411 And(Box<EventFilter>, Box<EventFilter>),
412 Or(Box<EventFilter>, Box<EventFilter>),
413}
414
415impl EventFilter {
416 fn try_matches(&self, item: &IotaEvent) -> IotaResult<bool> {
417 Ok(match self {
418 EventFilter::MoveEventType(event_type) => &item.struct_tag == event_type,
419 EventFilter::MoveEventField { path, value } => {
420 matches!(item.parsed_json.pointer(path), Some(v) if v == value)
421 }
422 EventFilter::Sender(sender) => &item.sender == sender,
423 EventFilter::Package(object_id) => &item.package_id == object_id,
424 EventFilter::MoveModule { package, module } => {
425 &item.transaction_module == module && &item.package_id == package
426 }
427 EventFilter::All(filters) => filters.iter().all(|f| f.matches(item)),
428 EventFilter::Any(filters) => filters.iter().any(|f| f.matches(item)),
429 EventFilter::And(f1, f2) => {
430 EventFilter::All(vec![*(*f1).clone(), *(*f2).clone()]).matches(item)
431 }
432 EventFilter::Or(f1, f2) => {
433 EventFilter::Any(vec![*(*f1).clone(), *(*f2).clone()]).matches(item)
434 }
435 EventFilter::Transaction(digest) => digest == &item.id.tx_digest,
436
437 EventFilter::TimeRange {
438 start_time,
439 end_time,
440 } => {
441 if let Some(timestamp) = &item.timestamp_ms {
442 start_time <= timestamp && end_time > timestamp
443 } else {
444 false
445 }
446 }
447 EventFilter::MoveEventModule { package, module } => {
448 item.struct_tag.module() == module
449 && &item.struct_tag.address() == package.as_address()
450 }
451 })
452 }
453
454 pub fn and(self, other_filter: EventFilter) -> Self {
455 Self::All(vec![self, other_filter])
456 }
457 pub fn or(self, other_filter: EventFilter) -> Self {
458 Self::Any(vec![self, other_filter])
459 }
460}
461
462impl Filter<IotaEvent> for EventFilter {
463 fn matches(&self, item: &IotaEvent) -> bool {
464 self.try_matches(item).unwrap_or_default()
465 }
466}
467
468pub trait Filter<T> {
469 fn matches(&self, item: &T) -> bool;
470}
471
472#[cfg(test)]
473mod test {
474 use super::*;
475
476 #[test]
477 fn bcs_event_test() {
478 let bytes = vec![0, 1, 2, 3, 4];
479 let untagged_base58 = r#"{"bcs":"12VfUX"}"#;
480 let tagged_base58 = r#"{"bcsEncoding":"base58","bcs":"12VfUX"}"#;
481 let tagged_base64 = r#"{"bcsEncoding":"base64","bcs":"AAECAwQ="}"#;
482
483 assert_eq!(
484 bytes,
485 serde_json::from_str::<BcsEvent>(untagged_base58)
486 .unwrap()
487 .into_bytes()
488 );
489 assert_eq!(
490 bytes,
491 serde_json::from_str::<BcsEvent>(tagged_base58)
492 .unwrap()
493 .into_bytes()
494 );
495 assert_eq!(
496 bytes,
497 serde_json::from_str::<BcsEvent>(tagged_base64)
498 .unwrap()
499 .into_bytes()
500 );
501
502 let event = serde_json::from_str::<BcsEvent>(tagged_base64).unwrap();
504 let json = serde_json::to_string(&event).unwrap();
505 let from_json = serde_json::from_str::<BcsEvent>(&json).unwrap();
506 assert_eq!(event, from_json);
507
508 let event = serde_json::from_str::<BcsEvent>(tagged_base58).unwrap();
510 let json = serde_json::to_string(&event).unwrap();
511 let from_json = serde_json::from_str::<BcsEvent>(&json).unwrap();
512 assert_eq!(event, from_json);
513 }
514}