Position Data Maintenance
This document describes the MoolahUserPosition entity and the services that maintain and consume it for Lista Lending. It is the data foundation for risk monitoring, liquidation alerts, emission (rewards), and borrow history.
1. Overview
MoolahUserPosition is the core entity for user positions in the Moolah lending protocol. It stores, per user per market:
Collateral and borrow amounts
Liquidation price (or liquidation price ratio)
Related identifiers (chain, market, user address)
This entity supports:
Risk monitoring — Track positions approaching liquidation.
Liquidation alerts — Identify and push alerts for positions at or over the liquidation threshold.
Emission (rewards) — Aggregate collateral by market for reward distribution weights.
Borrow / rate statistics — Daily and hourly borrow and rate reports.
2. Entity definition
There is exactly one position record per (user address, market).
Typical fields (names may differ in implementation):
userAddress
Wallet address
marketId
Market identifier (or equivalent)
chainId
Chain (e.g. BSC, Ethereum)
collateralAmount
Supplied collateral (human-readable, e.g. 18 decimals)
borrowShares
Borrow shares on-chain (raw Wei)
borrowedAmount
Actual borrowed amount (derived from shares + market state)
liquidationPriceRatio
Collateral/loan price ratio at which the position becomes liquidatable
updatedAt
Last update time
3. Data flow
4. Data writes
4.1 Event-driven write
Position data is updated by consuming on-chain events from Moolah (supply, withdraw, borrow, repay, liquidate, etc.). For each relevant event:
Fetch events — Read from the event store using a cursor (
lastId) to get unprocessed Moolah events in order.Read on-chain state — Use multicall to query current market state, user position, and oracle price.
Compute derived fields — From market rate type, compute actual borrowed amount and liquidation price ratio.
Write to DB — UPSERT by
(userAddress, marketId)(andchainIdif applicable).Write operation log — Optionally write to a user-operation log table.
Trigger alerts — If the event is a liquidation, trigger Telegram (or other) alerts.
Idempotency: Each event is tracked (e.g. Redis key with TTL 1500s) so the same event is not applied twice. Failed events are retried; after a max retry count they are skipped and processing continues.
4.2 Scheduled refresh
Because interest accrues continuously on-chain, borrowed amount and liquidation price change even when there are no new events. A scheduled job periodically refreshes all active positions:
Floating-rate markets — Read current rate from chain, compute accrued interest locally, then convert shares to assets to get current borrowed amount and liquidation price.
Fixed-rate (Broker) markets — Query the Broker contract for each user's total debt (
getBrokerTotalDebt(borrower, market)), then compute liquidation price.
5. Data reads
5.1 Borrow history
Daily — From the position table (and any snapshots), compute per-user daily average borrowed amount, collateral, and USD value; write to a daily borrow history table.
Hourly — Snapshot all positions hourly into an hourly borrow snapshot table.
5.2 Liquidation risk monitoring
The system periodically reads each position's liquidation price ratio and compares it to the current market price ratio from the oracle. When liquidation trigger price > current market price (i.e. position is liquidatable), it generates a risk summary and sends alerts (e.g. Telegram).
5.3 Emission (rewards)
Emission logic aggregates user collateral per market (from the position table) and uses it as the weight for reward distribution.
6. Related data
Market config — Chain, assets, oracle, LLTV, rate type (floating vs fixed).
Event log — Raw contract events for replay and auditing.
User operation log — Per-user action history (supply, borrow, repay, etc.).
7. Key concepts
7.1 Borrow shares vs actual borrowed amount
Borrow shares are the on-chain representation and change only on user actions (borrow, repay, liquidation).
Actual borrowed amount is derived from shares and market state (total assets, total shares, and for fixed-rate possibly Broker state) and increases over time with interest. The scheduled refresh keeps this field in sync.
7.2 Liquidation price ratio
The liquidation price ratio is the collateral/loan price ratio at which the position becomes liquidatable. When the oracle's current price ratio falls below this value, the position can be liquidated. It is derived from collateral, borrowed amount, and the market's LLTV.
7.3 Multi-chain
Moolah runs on BSC and Ethereum. Position data is usually stored in a single table with a chain identifier (e.g. chainId) so that downstream jobs and APIs can filter by chain.
8. Task checklist
Event consumer
Consume Moolah events and UPSERT positions
Scheduled refresh
Refresh borrowed amount and liquidation price for all active positions
Borrow history (daily)
Compute and store daily borrow/collateral stats
Borrow snapshot (hourly)
Snapshot positions for hourly stats
Liquidation monitor
Compare liquidation price to oracle price and send alerts
Emission aggregation
Aggregate collateral by market for reward weights
9. Notes
Precision: Collateral and borrowed amount are usually stored in human form (e.g. 18 decimals); borrow shares are often stored as raw Wei.
Read/write separation: Writes go to the primary DB; reads may use replicas. Replication lag should be considered for consistency.
Idempotency: Event processing uses Redis (or similar) plus UPSERT so the same event is not applied twice.
Rate type: Floating-rate and fixed-rate markets use different formulas for borrowed amount and liquidation price; the market config drives which path is used.
Last updated
Was this helpful?