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):

Field
Description

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:

  1. Fetch events — Read from the event store using a cursor (lastId) to get unprocessed Moolah events in order.

  2. Read on-chain state — Use multicall to query current market state, user position, and oracle price.

  3. Compute derived fields — From market rate type, compute actual borrowed amount and liquidation price ratio.

  4. Write to DB — UPSERT by (userAddress, marketId) (and chainId if applicable).

  5. Write operation log — Optionally write to a user-operation log table.

  6. 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.


  • 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

Task
Description

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?