Допис
Діліться своїми знаннями.
Using events for analytics in Sui Move
How do I properly emit events in Sui Move for off-chain indexing? Are there specific patterns or traits I should follow for structured logging?
- Move CLI
- Move
- Smart Contract
Відповіді
4In Sui, events are the primary mechanism for off-chain indexing. Unlike on-chain storage, which persists state in objects, events are lightweight, append-only logs that can be queried through RPC (e.g. suix_getEvents). They don’t affect consensus state directly but provide a structured way to capture what happened during a transaction.
How to Emit Events in Sui Move
- Define an event struct
Must have the drop and copy abilities (so it can be moved into the event stream).
By default, any struct you pass to event::emit is treated as an event.
module analytics::lending { use sui::event; use sui::tx_context::TxContext;
/// Event struct
struct LoanIssued has copy, drop {
borrower: address,
amount: u64,
loan_id: u64,
}
public entry fun issue_loan(borrower: address, amount: u64, loan_id: u64, ctx: &mut TxContext) {
// ... loan logic here ...
event::emit(LoanIssued { borrower, amount, loan_id });
}
}
Off-Chain Consumption
Wallets, explorers, or analytics platforms subscribe to events by package/module/event type.
For example, an indexer can call RPC with a filter:
{
"MoveEventType": "0x
Best Practices for Structured Logging
- Use strongly typed events
Define clear structs with field names (instead of unstructured strings).
This makes off-chain parsing consistent.
- Emit events only for “business logic” state changes
Don’t log trivial state like balance updates already observable on-chain.
Use them for actions: loan issued, NFT minted, auction settled, etc.
- Keep event payloads small
Gas costs scale with payload size.
Log identifiers (object IDs, addresses, amounts) instead of duplicating full state.
- Name events by action
Use verbs like LoanIssued, DepositMade, AuctionClosed.
Makes it easier for indexers and dashboards to track.
- Avoid redundant storage writes
If off-chain analytics can reconstruct history from events, don’t store extra state just for reporting.
Why Prefer Events Over On-Chain Queries?
Gas efficiency: Events don’t require mutating state; appending is cheaper than writing to storage.
Scalability: Indexers can query only the stream of relevant events instead of scanning entire object state.
Immutability: Events are append-only and tied to transaction digests, ensuring auditability.
Flexibility: You can extend analytics (e.g., track borrower behavior, trading volumes) without bloating the on-chain state.
How to Emit Events in Sui Move
- Define an Event Structure
Create a struct that represents the event payload.
Add the drop ability because events are not stored permanently on-chain; they are emitted and discarded.
module my_project::example {
use sui::event;
/// Define the event structure
struct PurchaseEvent has drop {
buyer: address,
item_id: u64,
price: u64,
}
public fun emit_purchase_event(buyer: address, item_id: u64, price: u64) {
let event = PurchaseEvent { buyer, item_id, price };
event::emit(event);
}
}
event::emit is the standard function for emitting events.
Best Practices for Structured Logging
- Use Strongly Typed Structs
Avoid raw strings or arbitrary maps.
Define fields explicitly for predictable indexing.
- Keep Events Lightweight
Emit only essential information (IDs, addresses, numeric values).
Do not include large data like metadata or JSON.
- Emit Events at Critical Points
Actions like mint, transfer, burn, or state transitions (e.g., auction closed, order executed).
- Version Your Event Structures
If you upgrade a module, consider adding version suffix (e.g., PurchaseEventV2) for backward compatibility.
- Ensure Deterministic Ordering
Events should reflect the order of operations for easy reconstruction of history off-chain.
✅ How to Query Events Off-Chain
Use Sui’s RPC or Indexer API:
sui_getEvents or GraphQL endpoints can query by:
Event type (module + struct name)
Sender address
Transaction digest
Example:
sui_getEvents(filter: { MoveEventType: "0x2::my_project::PurchaseEvent" })
Tools like Sui Indexer, Kafka event streams, or custom indexer can subscribe to these events for analytics.
Security Consideration
Do not rely on events for on-chain logic because they are not part of state.
Only use events for off-chain analytics, notifications, and dashboards.
When you’re building on Sui, events are the main way to expose structured data for off-chain analytics, dashboards, or marketplace activity tracking. Unlike state changes, which can be heavy and expensive to index, events are lightweight, append-only logs that off-chain indexers can subscribe to.
To emit an event in Move, you define a struct that represents the shape of your event and mark it with the drop
ability (so it doesn’t need to persist on-chain). Then you call event::emit<T>()
to publish it during execution. A simple pattern looks like this:
module myapp::analytics {
use sui::event;
use sui::tx_context::TxContext;
struct TransferEvent has drop {
sender: address,
recipient: address,
amount: u64,
}
public entry fun transfer(recipient: address, amount: u64, ctx: &mut TxContext) {
let sender = tx_context::sender(ctx);
// perform your logic here (debit/credit balances)
// emit the event for off-chain indexing
event::emit(TransferEvent { sender, recipient, amount });
}
}
Best practices for structured logging in Sui:
- Keep event fields flat and primitive (
address
,u64
,vector<u8>
) so off-chain indexers can easily parse them. - Emit one event per action instead of batching complex data into a single event. This makes filtering and aggregation easier.
- Use consistent naming across modules so different apps can reuse analytics tools (e.g., always call it
TransferEvent
orMintEvent
). - If your app evolves, consider versioning your events (e.g.,
V1
,V2
) instead of breaking old indexers.
Events don’t persist state on-chain, but they create a reliable stream of business actions (mints, sales, swaps, staking) that off-chain services can index into databases or analytics pipelines. For example, a marketplace can build a leaderboard of top traders by indexing SaleEvent
s.
When I design analytics in Sui Move, I rely on the event
system because it provides a lightweight and efficient way to expose structured data to off-chain indexers. To emit events, I first define a custom struct
that represents the event payload, and I ensure it derives the drop
ability since the event will only exist ephemerally on-chain. For example:
module my_dapp::analytics {
use sui::event;
struct UserAction has drop {
user: address,
action: vector<u8>,
timestamp: u64,
}
public fun emit_user_action(user: address, action: vector<u8>, timestamp: u64) {
let e = UserAction { user, action, timestamp };
event::emit(e);
}
}
I make sure to keep my event structs flat and serializable — avoiding deep nesting or unnecessary complexity — because this makes indexing and querying simpler off-chain.
For structured logging, I follow two patterns:
- Consistency in field names and types: I standardize naming across modules so my indexer can aggregate analytics without extra transformations.
- Granularity of events: Instead of emitting overly generic events, I design event types around meaningful business actions (e.g.,
UserAction
,AssetTransferred
,RewardClaimed
).
By following these practices, I can expose precise, structured logs to off-chain systems, making it easier to build dashboards, run analytics pipelines, and trace user behavior in real time.
Ви знаєте відповідь?
Будь ласка, увійдіть та поділіться нею.