Reading the payloads
Each event’stopics list is a constant vector of Soroban Symbols. No event in
this protocol promotes a field into the topic vector, so topics carry no data —
match on them exactly, in order.
Payload encoding is one of three shapes:
Only
UpdatePositionBatchEvent uses vec; only PoolMarketStateBatchEvent and
PoolMarketParamsBatchEvent use single-value; everything else uses map.
A nested #[contracttype] struct with named fields encodes as an
alphabetically-keyed map; one with unnamed fields (a tuple struct) encodes as
a vector in declaration order.
Scales. Never assume a scale. RAY is 1e27 (indexes, rates, utilization,
scaled share balances), WAD is 1e18 (USD values, health factors, sanity
bounds), bps is 10_000. “Raw asset units” means the token’s own decimals,
which are not carried in the event — read them from the token contract or
from PoolMarketParamsBatchEvent. Pool timestamps are milliseconds.
PositionAction values
PositionAction is a unit-only enum with #[repr(u32)], so it appears on the
wire as a plain u32. It tags each leg inside UpdatePositionBatchEvent.
Shared payload types
EventAccountAttributes
A tuple struct — a 3-entry vector: [owner: Address, spoke_id: u32, mode: EventPositionMode].
EventPositionMode is a u32 enum: None = 0 (a normal, non-strategy
position), Multiply = 1, Long = 2, Short = 3. It is the wire form of
PositionMode, with Normal mapped to None.
EventDepositDelta
A tuple struct — a 10-entry vector:
EventBorrowDelta
A tuple struct — a 6-entry vector: [action, hub_id, asset, scaled_amount (RAY), index_ray (RAY), amount (raw asset units)].
Controller events
UpdatePositionBatchEvent
- Topics:
["position", "batch_update"] - Format:
vec— a 4-entry vector, not a map. - Emitted by:
supply,withdraw,borrow,repay,liquidate,multiply,flash_position,swap_debt,swap_collateral,repay_debt_with_collateral,migrate_from_blend,update_account_threshold.
The event is not published when both lists are empty.
LiquidationEvent
- Topics:
["position", "liquidation"]· Format:map
This event carries no seizure or protocol-fee figure. Those live in the
accompanying batch’s
LiqSeize legs (gross) and, in share-credit mode, its
LiqCredit legs (net).
FlashLoanEvent
- Topics:
["position", "flash_loan"]· Format:map - Fields:
hub_id,asset,receiver,caller,amount(raw),fee(raw).
FlashPositionEvent
- Topics:
["position", "flash_position"]· Format:map - Fields:
account_id,hub_id,asset,receiver,caller,amount(principal requested from the debt mint),amount_received(tokens the receiver actually got, measured by balance delta),fee(always 0 — a flash position is zero-fee; the cost is the strategy debt it mints).
FlashPos; collateral
deposited from the callback is tagged Supply, identically to an ordinary
deposit. Do not read FlashPos as the collateral leg.
AccountDelegateEvent
- Topics:
["account", "delegate"]· Format:map - Fields:
account_id,owner,delegate,granted(bool). - Published only when the delegate list actually changed.
CleanBadDebtEvent
- Topics:
["debt", "bad_debt"]· Format:map - Fields:
account_id,total_borrow_usd_wad,total_collateral_usd_wad. - Emitted by
clean_bad_debt,force_socialize_bad_debt, and byliquidatewhen the post-liquidation account still holds eligible bad debt.
UpdatePositionBatchEvent for
the cleanup itself.
Market events
Both carry the flat rate-curve fields (
max_borrow_rate, base_borrow_rate,
slope1–slope3, mid_utilization, optimal_utilization, max_utilization,
all RAY; reserve_factor in bps) plus hub_id and the asset.
CreateMarketEvent also carries market_address. Neither copies the
flash-loan flag, the flash-loan fee, or asset decimals — read those from
PoolMarketParamsBatchEvent.
Strategy events
Configuration events
UpdateSpokeAssetEvent carries the asset’s full post-change SpokeAssetConfig
map, including all three halt flags (paused, frozen, no_seize).
Pool events
PoolMarketStateBatchEvent
- Topics:
["market", "batch_state_update"] - Format:
single-value— the payload is the vector of rows. There is no wrapping map and noupdateskey. - Emitted by:
supply,borrow,withdraw,repay,seize_positions,net_settle,update_indexes,claim_revenue,flash_loan,recapitalize, andcreate_strategy. Not published when the snapshot list is empty.
PoolMarketParamsBatchEvent
- Topics:
["market", "batch_params_update"] - Format:
single-value— the payload is the vector of rows. - Emitted by:
create_marketandupdate_params; both publish exactly one row.
hub_id, asset, and params: MarketParamsRaw — the
market’s full post-change parameters, including flash-loan settings and asset
decimals.
StrategyFeeEvent
- Topics:
["strategy", "fee"]· Format:map - Fields:
hub_id,asset,amount(gross strategy principal),fee(withheld),amount_sent(amount − fee). - Published only when the fee is non-zero.
Governance events
Price-aggregator events
UpdateAssetOracleEvent — topics ["config", "asset_oracle"], format map,
fields key: PriceKey and oracle: AssetOracle. Emitted by set_oracle,
set_sanity_band, and set_tolerance.
DeFindex-strategy events
HarvestEvent — topics ["strategy", "harvest"], format map, fields from,
amount (always 0; harvest moves no funds), and price_per_share
(12 decimals, floor-rescaled from the RAY supply index).
Indexing notes
- Topic names are contract API. Treat renames as breaking changes.
- Store
HubAssetKeyfields together; do not index by token address alone — the same token on two hubs is two markets. - Key
swap_debtopens onSwDebtR, which tags both legs. - Do not assume bad-debt cleanup precedes the position batch: every
UpdatePositionBatchEventis published beforeCleanBadDebtEvent. - A
Credit(0)account has no creation event. It is announced only through the second batch’saccount_attributes, and returned to the caller. An indexer that discovers accounts from a creation event alone will miss it. flash_positionrefunds are silent: no refund event exists anywhere, andFlashPositionEventcarries no refund field. The only on-chain trace is the token contract’s owntransferevent, controller to caller.- Position ownership changes are observable only through the position-NFT contract’s standard non-fungible transfer semantics; this protocol defines no event for them.

