Protocol Registries
The contract maintains two independent registries using the indexed mapping + soft-delete pattern: one for lending protocols and one for perp protocols.Protocol Types
Lending Protocols
| Constant | Value | Protocol |
|---|---|---|
LENDING_TYPE_AAVE_V3 | 0 | Aave V3 |
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: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. Thescan_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
initialize(risk_threshold) -> Result<()>
initialize(risk_threshold) -> Result<()>
Set the caller as owner and configure the initial risk threshold. Can only
be called once.
add_lending_protocol(pool_address, protocol_type) -> Result<U256>
add_lending_protocol(pool_address, protocol_type) -> Result<U256>
Register a lending protocol pool. Owner only.
- Rejects zero addresses.
- Returns the new protocol index.
- Emits
LendingProtocolAdded(index, poolAddress, protocolType).
remove_lending_protocol(index) -> Result<()>
remove_lending_protocol(index) -> Result<()>
Soft-delete a lending protocol. Owner only.
- Reverts if index is out of bounds or already removed.
- Emits
LendingProtocolRemoved(index, poolAddress).
add_perp_protocol(reader_address, protocol_type) -> Result<U256>
add_perp_protocol(reader_address, protocol_type) -> Result<U256>
Register a perp protocol reader. Owner only. Same pattern as lending.
- Emits
PerpProtocolAdded(index, readerAddress, protocolType).
remove_perp_protocol(index) -> Result<()>
remove_perp_protocol(index) -> Result<()>
Soft-delete a perp protocol. Owner only.
- Emits
PerpProtocolRemoved(index, readerAddress).
get_health_factor(account) -> Result<U256>
get_health_factor(account) -> Result<U256>
Query the lowest health factor for an account across all active lending
protocols. Reverts if no lending protocols are registered.
scan_accounts(accounts) -> Result<Vec<(Address, U256)>>
scan_accounts(accounts) -> Result<Vec<(Address, U256)>>
Scan an arbitrary list of addresses. Returns only those with a health factor
below the risk threshold, as
(address, healthFactor) tuples.scan_tracked_accounts() -> Result<Vec<(Address, U256)>>
scan_tracked_accounts() -> Result<Vec<(Address, U256)>>
Scan the on-chain tracked account list. Emits
AccountAtRisk events for
each at-risk position found, including a block timestamp.add_account(account) -> Result<()>
add_account(account) -> Result<()>
Add an address to the tracked list. Owner only. Reverts at the 500-account
cap.
- Emits
AccountAdded(account).
remove_account(account) -> Result<()>
remove_account(account) -> Result<()>
Remove an address from the tracked list using swap-and-pop. Owner only.
- Emits
AccountRemoved(account).
set_threshold(new_threshold) -> Result<()>
set_threshold(new_threshold) -> Result<()>
Update the risk threshold. Owner only.
- Emits
ThresholdUpdated(oldThreshold, newThreshold).
tracked_count() -> U256
tracked_count() -> U256
Number of currently tracked accounts.
threshold() -> U256
threshold() -> U256
Current risk threshold value.
Events
| Event | Indexed Fields | Data Fields |
|---|---|---|
AccountAtRisk | account | healthFactor, timestamp |
AccountAdded | account | — |
AccountRemoved | account | — |
ThresholdUpdated | — | oldThreshold, newThreshold |
LendingProtocolAdded | index | poolAddress, protocolType |
LendingProtocolRemoved | index | poolAddress |
PerpProtocolAdded | index | readerAddress, protocolType |
PerpProtocolRemoved | index | readerAddress |
Off-Chain Integration
The off-chain agent uses this contract in two ways:- On-demand health checks — when a user asks “check my health factor”,
the agent calls
get_health_factorfor their address. - Periodic scanning — a background job calls
scan_tracked_accountsand surfacesAccountAtRiskevents to users via notifications.
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)