StellarLend emits a stable, versioned event schema so that indexers and integrators can decode events safely across contract upgrades.
The versioning strategy is minimal by design:
- Each contract defines its own
EVENT_SCHEMA_VERSION: u32constant in its own event module. - Versioned event structs carry a
schema_version: u32field populated with the contract-local constant at emit time. - A
SchemaVersionEventis emitted once duringinitialize, giving indexers an on-chain anchor for the version active at deployment for that contract.
This document covers the event schema versioning policy for each contract
separately. contracts/lending and contracts/hello-world maintain independent
schema versioning, so indexers should track each contract's schema version
based on the contract address being indexed.
| Struct | Since version | Notes |
|---|---|---|
SchemaVersionEvent |
1 | Emitted once on initialize. |
DepositEvent |
1 | Emitted when a user deposits collateral. Includes user, amount, new balance, and timestamp. |
WithdrawEvent |
1 | Emitted when a user withdraws collateral. Includes user, amount, new balance, and timestamp. |
BorrowEvent |
1 | Emitted when a user borrows against collateral. Includes user, amount, new debt principal, and timestamp. |
RepayEvent |
1 | Emitted when a user repays debt. Includes user, amount, new debt principal, and timestamp. |
LiquidateEvent |
1 | Emitted when a liquidator liquidates an undercollateralized position. Includes liquidator, borrower, repaid debt, seized collateral, remaining debt, remaining collateral, and timestamp. |
LiquidationEventV1 |
1 | Versioned liquidation with post-liquidation borrower snapshot. |
BorrowerHealthEventV1 |
1 | Borrower health snapshot emitted alongside position updates. |
DepositEvent |
1 | Versioned deposit event with user and new collateral balance. |
WithdrawEvent |
1 | Versioned withdraw event with user and new collateral balance. |
BorrowEvent |
1 | Versioned borrow event with user and new debt balance. |
RepayEvent |
1 | Versioned repay event with user and new debt balance. |
AmmSwapEventV1 |
1 | Versioned AMM swap event with stable amm/v1 topics. |
AmmLiquidityAddedEventV1 |
1 | Versioned AMM add-liquidity event with stable amm/v1 topics. |
AmmLiquidityRemovedEventV1 |
1 | Versioned AMM remove-liquidity event with stable amm/v1 topics. |
AMM mutation events must publish a stable, versioned topic tuple:
(env.events().publish(
(Symbol::new(&env, "amm"), Symbol::new(&env, "v1"), Symbol::new(&env, "swap")),
event_data
));Supported AMM event kinds for version v1:
swapadd_liquidityremove_liquidity
Each versioned AMM event payload must include schema_version: 1 and an
explicit event field that matches the final topic segment.
All other events are unversioned (no schema_version field). They follow an
additive-only policy: new fields may be appended but existing fields will not
be removed or reordered within a major version.
When a new contract instance is deployed, the first event emitted is
SchemaVersionEvent. Persist version from this event alongside the contract
address.
{
"event_name": "SchemaVersionEvent",
"schema_version": 1,
"timestamp": 1714176000
}For events that carry a schema_version field, always read it before
decoding the rest of the payload:
def decode_event(raw):
version = raw.get("schema_version")
if version == 1:
return decode_v1(raw)
elif version == 2:
return decode_v2(raw)
else:
raise UnknownSchemaVersion(version)Events without a schema_version field are legacy / unversioned events.
Decode them using the field set documented at the time of the contract version
you are indexing.
- Adding a new unversioned event struct.
- Appending optional fields to an existing unversioned event (indexers must tolerate unknown fields).
- Adding or removing a field on a versioned event struct.
- Changing the type of any field on a versioned event struct.
Procedure for a breaking change:
- Increment
EVENT_SCHEMA_VERSIONinevents.rs. - Introduce a new struct (e.g.
FooEventV2) with the updated schema. - Emit both the old and new struct for one upgrade cycle so indexers can migrate without downtime.
- After all known indexers have migrated, retire the old struct in the following upgrade.
- Update this document and the table above.
// Before (version 1)
pub struct LiquidationEventV1 {
pub schema_version: u32,
// ... existing fields
}
// After (version 2) – introduce V2, keep V1 for one cycle
pub struct LiquidationEventV2 {
pub schema_version: u32,
// ... existing fields
pub new_field: i128, // ← new
}Bump EVENT_SCHEMA_VERSION to 2 and emit both LiquidationEventV1 and
LiquidationEventV2 during the transition cycle.
The indexing_system crate surfaces schema_version as an optional field on
both CreateEvent and Event:
pub struct CreateEvent {
// ...
/// None for unversioned events; Some(n) for versioned events.
pub schema_version: Option<u32>,
}The parser (indexing_system/src/parser.rs) automatically extracts
schema_version from the decoded JSON payload when the field is present.
contracts/lending/src/events.rs–EVENT_SCHEMA_VERSIONconstant, all event structs, and emit functions.contracts/lending/src/lib.rs– Event emission ininitialize,deposit,withdraw,borrow,repay, andliquidate.contracts/lending/src/events_test.rs– Comprehensive event emission tests for all core operations.contracts/hello-world/src/events.rs–EVENT_SCHEMA_VERSIONconstant,SchemaVersionEvent,emit_schema_version.contracts/hello-world/src/tests/events_test.rs–test_schema_version_event_emitted,test_versioned_events_carry_current_schema_version.indexing_system/src/models.rs–CreateEvent.schema_version,Event.schema_version.indexing_system/src/parser.rs– automatic extraction ofschema_version.docs/storage.md– storage layout and migration strategy.docs/UPGRADE_AUTHORIZATION.md– upgrade authorization boundaries.