Events

Sails offers a mechanism to emit events from a service while processing commands. These events notify off-chain subscribers about state changes.

Events are declared per service via the events argument to #[service]. The event payload is a Rust enum whose variants describe each event shape. Annotate the enum with #[event] (registers it as an event type) and #[sails_type] (wires the SCALE codec and scale-info traits with the correct re-export paths). When a service declares events = SomeEnum, #[service] generates an emit_event method on the service exposure that the command body calls to publish events.

use sails_rs::{cell::RefCell, prelude::*};

pub struct CounterData {
    counter: u32,
}

impl CounterData {
    pub const fn new(counter: u32) -> Self {
        Self { counter }
    }
}

// `#[event]` registers the enum as an event payload type.
// `#[sails_type]` wires up SCALE codec + type-info traits with the
// correct re-export paths (no manual `#[codec(crate=...)]` needed).
#[event]
#[sails_type]
#[derive(Clone, Debug, PartialEq)]
pub enum MyCounterEvent {
    Incremented(u32),
}

pub struct MyCounter {
    data: RefCell<CounterData>,
}

#[service(events = MyCounterEvent)]
impl MyCounter {
    pub fn new(initial: u32) -> Self {
        Self { data: RefCell::new(CounterData::new(initial)) }
    }

    #[export]
    pub fn increment(&mut self) -> u32 {
        let value = {
            let mut data = self.data.get_mut();
            data.counter += 1;
            data.counter
        };

        // `emit_event` is generated by `#[service(events = ...)]`.
        // The event is only published when the command completes
        // successfully (it rides on the same message-transmission
        // mechanism as any other Gear reply).
        self.emit_event(MyCounterEvent::Incremented(value)).unwrap();
        value
    }
}

Internally, events ride on the same Gear message-transmission machinery as any other reply. As a result, an event is only published when the command that emitted it completes successfully — a panic, trap, or error return cancels every event queued during the call.