Skip to main content

๐Ÿ“ก Events

Events are how pallet-xp makes state changes visible across the runtime.

They are used by:

  • wallets
  • block explorers
  • indexers
  • frontend apps
  • test environments
  • debugging tools

They are emitted through:

deposit_event(...)

and become part of normal Substrate runtime visibility.

But in pallet-xp, not all events are treated equally.

That behavior is controlled by:

type EmitEvents: Get<bool>

inside the Config trait.


Event Strategyโ€‹

There are two event layers:

LayerPurpose
Extrinsic EventsRequired protocol-visible state transitions
Internal Lifecycle EventsVerbose XP mutation visibility

This distinction is important.


EmitEvents = false (Production)โ€‹

type EmitEvents = ConstBool<false>;

When disabled:

only extrinsic events are emitted

This is the recommended production setup.

Why?

Because emitting every internal XP mutation:

  • increases execution weight
  • adds unnecessary event noise
  • makes indexing expensive
  • creates operational overhead

Production systems should prefer:

RPC queries + minimal critical events

not verbose mutation logs.


EmitEvents = true (Development)โ€‹

type EmitEvents = ConstBool<true>;

When enabled:

all lifecycle mutation events are emitted

including:

  • XP earned
  • XP locked
  • XP reserved
  • XP slashed
  • ownership updates
  • reaping
  • internal state changes

This is ideal for:

  • local development
  • testnets
  • mock runtimes
  • QA
  • benchmarking validation
  • quick debugging

It provides fast introspection without needing custom RPC inspection.


Extrinsic Eventsโ€‹

These are protocol-level events caused by dispatchable calls.

These should remain visible.

EventTriggered ByMeaning
XpOwnerhandover() / force_handover()Ownership assigned or transferred
XpReapdispose()XP permanently reaped
GenesisConfigUpdatedforce_genesis_config()Runtime XP parameters changed
Xpinspect_my_xp() (dev only)Inspector read result
XpOfOwnerinspect_xp_keys_of() (dev only)Inspector ownership snapshot

Important:

When EmitEvents = false, these still remain visible where explicitly emitted by extrinsics like:

  • handover()
  • dispose()
  • root administrative calls

because protocol visibility must remain intact.


Internal Lifecycle Eventsโ€‹

These are emitted by trait methods during internal XP mutation flows.

They are useful for development, but usually too noisy for production.

EventTriggered ByMeaning
Xpcreate / direct mutationXP created or updated
XpEarnearn_xp()XP earned
XpSlashslash operationsXP reduced
XpLockset_lock()XP locked
XpLockBurnlock removalLock removed
XpLockSlashlock slashLocked XP slashed
XpReserveset_reserve()XP reserved
XpReserveSlashreserve slashReserved XP slashed
XpOwnerinternal owner changesOwnership event
XpReapinternal reap logicReap completion

These are excellent for debugging:

Why didn't XP increase?
What triggered this lock?
Did reserve happen correctly?

but should not be the primary production read model.


Listeners vs Eventsโ€‹

A common misunderstanding:

EmitEvents.false != Hooks disabled

Even when events are disabled:

  • listeners still execute
  • Config::Extensions still run
  • lifecycle hooks still fire

Only event emission changes. This is explicitly guaranteed by the pallet design.

So:

Events = visibility

Listeners = runtime reactions

Events make state changes observable. Listeners allow runtime logic to react to those changes.

They are separate systems.

A common anti-pattern is building runtime logic by inspecting emitted events and adding conditional behavior around them.

That should be avoided.

Instead of:

Emit event -> inspect event -> trigger logic

the correct design is:

State change -> listener hook -> runtime reaction

This is exactly why listeners are provided. They allow pallets and integrations to react directly to XP lifecycle changes without relying on event parsing.

This is:

  • cleaner
  • cheaper
  • deterministic
  • safer for production
  • independent of Config::EmitEvents

Use events for visibility and Use listeners for behavior.


All Events Tableโ€‹

EventFieldsCategoryRepresents
Xpid, xpInspector / InternalCurrent XP value for an identity
XpOwnerid, ownerExtrinsic + InternalOwnership assignment or transfer
XpOfOwnerowner, idsInspectorAll XP identities owned by an account
XpEarnid, amountInternalXP earned through progression
XpReapidExtrinsic + InternalPermanent deletion of an XP identity
XpSlashid, amountInternalXP reduced due to slash logic
XpLockid, amountInternalXP locked and made temporarily unavailable
XpLockBurnid, amountInternalLocked XP removed or released
XpLockSlashid, amountInternalLocked XP reduced through slashing
XpReserveid, amountInternalXP moved into reserved state
XpReserveSlashid, amountInternalReserved XP reduced through slashing
GenesisConfigUpdatedconfigRoot ExtrinsicGlobal XP configuration updated by root

This is the complete pallet event surface.


๐Ÿš€ Next Stepsโ€‹

Now that event visibility is clear, the next step is understanding how production clients should query XP using runtime APIs and UI integrations.

๐Ÿ‘‰ Core -> RPC + UI