帖子
分享您的知识。
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.
你知道答案吗?
请登录并分享。
Move is an executable bytecode language used to implement custom transactions and smart contracts.