Skip to main content

aptos_sdk/types/
events.rs

1//! Event types.
2//!
3//! Events are emitted by Move modules and can be used to track
4//! on-chain activity without reading full transaction data.
5//!
6//! These types deserialize the JSON shape returned by the Aptos REST
7//! (fullnode) API. In particular, the API encodes every `u64` value as a JSON
8//! string (e.g. `"42"`), and the on-chain event-handle GUID nests its fields
9//! under an `id` object (`{"id":{"addr":...,"creation_num":...}}`). The types
10//! here match those shapes; for convenience the string-encoded integers also
11//! accept a plain JSON number when deserializing.
12
13use crate::types::{AccountAddress, HashValue};
14use serde::{Deserialize, Serialize};
15use std::fmt;
16
17/// A unique identifier for an event stream.
18///
19/// Event keys are composed of a creation number and an address.
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
21pub struct EventKey {
22    /// The creation number (unique within the account).
23    pub creation_number: u64,
24    /// The account address that owns this event stream.
25    pub account_address: AccountAddress,
26}
27
28impl EventKey {
29    /// Creates a new event key.
30    pub fn new(creation_number: u64, account_address: AccountAddress) -> Self {
31        Self {
32            creation_number,
33            account_address,
34        }
35    }
36}
37
38impl fmt::Display for EventKey {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        write!(f, "{}:{}", self.account_address, self.creation_number)
41    }
42}
43
44/// A handle to an event stream stored on chain.
45///
46/// This matches the REST API shape of an on-chain `0x1::event::EventHandle`,
47/// where the `guid` is a `0x1::guid::GUID` nested under an `id` object:
48/// `{"counter":"N","guid":{"id":{"addr":"0x..","creation_num":"N"}}}`.
49#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
50pub struct EventHandle {
51    /// The number of events that have been emitted to this handle.
52    #[serde(with = "crate::types::string_num")]
53    pub counter: u64,
54    /// The globally unique ID for this event stream.
55    pub guid: EventHandleGuid,
56}
57
58/// The GUID of an on-chain event handle.
59///
60/// Mirrors the REST API's `0x1::guid::GUID`, which wraps the identifying
61/// fields under an `id` object: `{"id":{"addr":...,"creation_num":...}}`.
62#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
63pub struct EventHandleGuid {
64    /// The inner identifier (address + creation number).
65    pub id: EventHandleGuidId,
66}
67
68/// The inner identifier of an [`EventHandleGuid`] (`0x1::guid::ID`).
69#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
70pub struct EventHandleGuidId {
71    /// The account address that created the event stream.
72    pub addr: AccountAddress,
73    /// The creation number (unique within the creating account).
74    #[serde(with = "crate::types::string_num")]
75    pub creation_num: u64,
76}
77
78/// A globally unique identifier for an event stream.
79///
80/// This matches the shape used by the REST API for an emitted [`Event`]'s
81/// `guid` field (`{"creation_number":...,"account_address":...}`), which
82/// differs from the nested [`EventHandleGuid`] shape used inside on-chain
83/// event handles.
84#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
85pub struct EventGuid {
86    /// The creation number.
87    #[serde(with = "crate::types::string_num")]
88    pub creation_number: u64,
89    /// The account address.
90    pub account_address: AccountAddress,
91}
92
93/// An event emitted during transaction execution.
94#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
95pub struct Event {
96    /// The globally unique identifier for this event.
97    #[serde(default, skip_serializing_if = "Option::is_none")]
98    pub guid: Option<EventGuid>,
99    /// The sequence number of this event within its stream.
100    #[serde(with = "crate::types::string_num")]
101    pub sequence_number: u64,
102    /// The type of the event data.
103    #[serde(rename = "type")]
104    pub typ: String,
105    /// The event data as JSON.
106    pub data: serde_json::Value,
107}
108
109impl Event {
110    /// Returns the event type as a string.
111    pub fn event_type(&self) -> &str {
112        &self.typ
113    }
114
115    /// Tries to deserialize the event data into a specific type.
116    ///
117    /// # Errors
118    ///
119    /// Returns an error if the event data cannot be deserialized into the requested type.
120    pub fn data_as<T: for<'de> Deserialize<'de>>(&self) -> Result<T, serde_json::Error> {
121        serde_json::from_value(self.data.clone())
122    }
123}
124
125/// A versioned event from the indexer (includes transaction context).
126#[allow(dead_code)] // Public API for users
127#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
128pub struct VersionedEvent {
129    /// The transaction version that emitted this event.
130    #[serde(with = "crate::types::string_num")]
131    pub version: u64,
132    /// The event itself.
133    #[serde(flatten)]
134    pub event: Event,
135    /// The transaction hash (optional, from indexer).
136    #[serde(default, skip_serializing_if = "Option::is_none")]
137    pub transaction_hash: Option<HashValue>,
138}
139
140/// Common event types in the Aptos framework.
141#[allow(dead_code)] // Public API constants for users
142pub mod framework {
143    /// Event type for coin deposits.
144    pub const DEPOSIT_EVENT: &str = "0x1::coin::DepositEvent";
145    /// Event type for coin withdrawals.
146    pub const WITHDRAW_EVENT: &str = "0x1::coin::WithdrawEvent";
147    /// Event type for account creation.
148    pub const ACCOUNT_CREATE_EVENT: &str = "0x1::account::CreateAccountEvent";
149    /// Event type for key rotation.
150    pub const KEY_ROTATION_EVENT: &str = "0x1::account::KeyRotationEvent";
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_event_key() {
159        let key = EventKey::new(42, AccountAddress::ONE);
160        assert_eq!(key.creation_number, 42);
161        assert_eq!(key.account_address, AccountAddress::ONE);
162    }
163
164    #[test]
165    fn test_event_key_display() {
166        let key = EventKey::new(42, AccountAddress::ONE);
167        let display = format!("{key}");
168        assert!(display.contains("42"));
169        assert!(display.contains(':'));
170    }
171
172    #[test]
173    fn test_event_deserialization() {
174        let json = r#"{
175            "sequence_number": 1,
176            "type": "0x1::coin::DepositEvent",
177            "data": {"amount": "1000"}
178        }"#;
179
180        let event: Event = serde_json::from_str(json).unwrap();
181        assert_eq!(event.sequence_number, 1);
182        assert_eq!(event.typ, "0x1::coin::DepositEvent");
183    }
184
185    #[test]
186    fn test_event_type() {
187        let json = r#"{
188            "sequence_number": 1,
189            "type": "0x1::coin::DepositEvent",
190            "data": {"amount": "1000"}
191        }"#;
192
193        let event: Event = serde_json::from_str(json).unwrap();
194        assert_eq!(event.event_type(), "0x1::coin::DepositEvent");
195    }
196
197    #[test]
198    fn test_event_data_as() {
199        #[derive(serde::Deserialize, Debug, PartialEq)]
200        struct DepositEvent {
201            amount: String,
202        }
203
204        let json = r#"{
205            "sequence_number": 1,
206            "type": "0x1::coin::DepositEvent",
207            "data": {"amount": "1000"}
208        }"#;
209
210        let event: Event = serde_json::from_str(json).unwrap();
211        let data: DepositEvent = event.data_as().unwrap();
212        assert_eq!(data.amount, "1000");
213    }
214
215    #[test]
216    fn test_event_handle_deserialization() {
217        // Realistic fullnode shape: string-encoded integers and a nested
218        // `guid.id` object.
219        let json = r#"{
220            "counter": "100",
221            "guid": {
222                "id": {
223                    "addr": "0x1",
224                    "creation_num": "5"
225                }
226            }
227        }"#;
228
229        let handle: EventHandle = serde_json::from_str(json).unwrap();
230        assert_eq!(handle.counter, 100);
231        assert_eq!(handle.guid.id.creation_num, 5);
232        assert_eq!(handle.guid.id.addr, AccountAddress::ONE);
233    }
234
235    #[test]
236    fn test_event_handle_roundtrips_to_api_shape() {
237        let handle = EventHandle {
238            counter: 7,
239            guid: EventHandleGuid {
240                id: EventHandleGuidId {
241                    addr: AccountAddress::ONE,
242                    creation_num: 3,
243                },
244            },
245        };
246        let json = serde_json::to_value(&handle).unwrap();
247        // Integers must serialize back as strings to match the API.
248        assert_eq!(json["counter"], serde_json::json!("7"));
249        assert_eq!(json["guid"]["id"]["creation_num"], serde_json::json!("3"));
250        let back: EventHandle = serde_json::from_value(json).unwrap();
251        assert_eq!(back, handle);
252    }
253
254    #[test]
255    fn test_event_deserialization_api_shape() {
256        // Realistic fullnode event: u64 fields are JSON strings and the guid
257        // uses the {creation_number, account_address} shape.
258        let json = r#"{
259            "guid": {
260                "creation_number": "2",
261                "account_address": "0x1"
262            },
263            "sequence_number": "42",
264            "type": "0x1::coin::DepositEvent",
265            "data": {"amount": "1000"}
266        }"#;
267
268        let event: Event = serde_json::from_str(json).unwrap();
269        assert_eq!(event.sequence_number, 42);
270        assert_eq!(event.typ, "0x1::coin::DepositEvent");
271        let guid = event.guid.expect("guid present");
272        assert_eq!(guid.creation_number, 2);
273        assert_eq!(guid.account_address, AccountAddress::ONE);
274    }
275
276    #[test]
277    fn test_event_guid() {
278        let guid = EventGuid {
279            creation_number: 10,
280            account_address: AccountAddress::ONE,
281        };
282        assert_eq!(guid.creation_number, 10);
283        assert_eq!(guid.account_address, AccountAddress::ONE);
284    }
285
286    #[test]
287    fn test_versioned_event_deserialization() {
288        let json = r#"{
289            "version": 12345,
290            "sequence_number": 1,
291            "type": "0x1::coin::DepositEvent",
292            "data": {"amount": "1000"}
293        }"#;
294
295        let event: VersionedEvent = serde_json::from_str(json).unwrap();
296        assert_eq!(event.version, 12345);
297        assert_eq!(event.event.sequence_number, 1);
298    }
299
300    #[test]
301    fn test_framework_event_constants() {
302        assert_eq!(framework::DEPOSIT_EVENT, "0x1::coin::DepositEvent");
303        assert_eq!(framework::WITHDRAW_EVENT, "0x1::coin::WithdrawEvent");
304        assert_eq!(
305            framework::ACCOUNT_CREATE_EVENT,
306            "0x1::account::CreateAccountEvent"
307        );
308        assert_eq!(
309            framework::KEY_ROTATION_EVENT,
310            "0x1::account::KeyRotationEvent"
311        );
312    }
313}