Skip to main content
The Liquidation Monitor is a Stylus contract that scans user positions across registered lending and perpetual protocols, identifies accounts below a configurable health factor threshold, and emits events for at-risk positions.

Protocol Registries

The contract maintains two independent registries using the indexed mapping + soft-delete pattern: one for lending protocols and one for perp protocols.
sol_storage! {
    #[entrypoint]
    pub struct LiquidationMonitor {
        address owner;
        address[] tracked_accounts;
        uint256 risk_threshold;

        // Lending protocol registry
        uint256 lending_count;
        mapping(uint256 => address) lending_pools;
        mapping(uint256 => uint256) lending_types;
        mapping(uint256 => bool) lending_active;

        // Perp protocol registry
        uint256 perp_count;
        mapping(uint256 => address) perp_readers;
        mapping(uint256 => uint256) perp_types;
        mapping(uint256 => bool) perp_active;
    }
}

Protocol Types

Lending Protocols

ConstantValueProtocol
LENDING_TYPE_AAVE_V30Aave V3
Calls IAavePool.getUserAccountData(user) to retrieve the health factor. Additional types (Compound V3, Radiant) are reserved for future use.

Perp Protocols

The perp registry is structurally complete but the health check dispatch is reserved for future implementation. Protocol type constants for GMX V2 and Kyan are defined but not yet wired to on-chain readers.

Health Factor Scanning

The core scanning logic iterates over all active lending protocols for a given account and returns the lowest health factor found:
pub fn get_health_factor(&self, account: Address) -> Result<U256, Vec<u8>> {
    // Iterates active lending protocols
    // Dispatches to protocol-specific interface (Aave V3)
    // Returns the minimum health factor across all protocols
}
Health factors are 18-decimal fixed-point values. A value of 1_000_000_000_000_000_000 (1.0) means the position is at the liquidation boundary. The default risk threshold is 1_100_000_000_000_000_000 (1.1).

Tracked Accounts

The contract maintains an on-chain list of accounts to monitor, capped at 500 addresses. The scan_tracked_accounts method iterates over this list and emits AccountAtRisk events for any account whose health factor falls below the threshold. Account removal uses swap-and-pop — the last element replaces the removed element, keeping the operation O(1).

Public Methods

Set the caller as owner and configure the initial risk threshold. Can only be called once.
Register a lending protocol pool. Owner only.
  • Rejects zero addresses.
  • Returns the new protocol index.
  • Emits LendingProtocolAdded(index, poolAddress, protocolType).
Soft-delete a lending protocol. Owner only.
  • Reverts if index is out of bounds or already removed.
  • Emits LendingProtocolRemoved(index, poolAddress).
Register a perp protocol reader. Owner only. Same pattern as lending.
  • Emits PerpProtocolAdded(index, readerAddress, protocolType).
Soft-delete a perp protocol. Owner only.
  • Emits PerpProtocolRemoved(index, readerAddress).
Query the lowest health factor for an account across all active lending protocols. Reverts if no lending protocols are registered.
Scan an arbitrary list of addresses. Returns only those with a health factor below the risk threshold, as (address, healthFactor) tuples.
Scan the on-chain tracked account list. Emits AccountAtRisk events for each at-risk position found, including a block timestamp.
Add an address to the tracked list. Owner only. Reverts at the 500-account cap.
  • Emits AccountAdded(account).
Remove an address from the tracked list using swap-and-pop. Owner only.
  • Emits AccountRemoved(account).
Update the risk threshold. Owner only.
  • Emits ThresholdUpdated(oldThreshold, newThreshold).
Number of currently tracked accounts.
Current risk threshold value.

Events

EventIndexed FieldsData Fields
AccountAtRiskaccounthealthFactor, timestamp
AccountAddedaccount
AccountRemovedaccount
ThresholdUpdatedoldThreshold, newThreshold
LendingProtocolAddedindexpoolAddress, protocolType
LendingProtocolRemovedindexpoolAddress
PerpProtocolAddedindexreaderAddress, protocolType
PerpProtocolRemovedindexreaderAddress

Off-Chain Integration

The off-chain agent uses this contract in two ways:
  1. On-demand health checks — when a user asks “check my health factor”, the agent calls get_health_factor for their address.
  2. Periodic scanning — a background job calls scan_tracked_accounts and surfaces AccountAtRisk events to users via notifications.
The perp protocol registry is structurally complete but health check dispatch for perp protocols is not yet implemented. Adding a perp protocol will register it in storage but it will not be included in health factor calculations until dispatch logic is wired.

Test Coverage

The contract has 43 tests covering:
  • Initialization (owner set, double-init rejection)
  • Access control (owner pass, stranger rejection)
  • Account management (add, remove, cap enforcement, swap-and-pop)
  • Threshold updates and event emission
  • Health factor queries with mocked Aave V3 responses
  • Multi-account scanning (at-risk detection, healthy filtering, boundary values)
  • Tracked account scanning with event verification
  • Lending protocol registry (add, remove, soft-delete, event emission)
  • Perp protocol registry (add, remove, access control)
  • Multi-protocol health factor (returns lowest across pools)
  • Edge cases (no protocols registered, all protocols removed, empty input)