Aave Health Factor

Monitoring Aave user health

Every Aave user has an associated health factor. The health factor is a number that represents the user's collateralization ratio, i.e. the ratio between the value of the user's collateral and the value of the user's debt (multiplied by a certain factor).

If the health factor drops below 1, the user is considered undercollateralized and can be liquidated! Naturally this is an extremely important metric for Aave users and a great opportunity for a SendBlocks function!

Here we present two approaches to monitor the health factor depending on the occurrence that causes the function to run: wallet actions and oracle updates.

Wallet Actions Trigger

The first approach is to set a trigger on your wallet's address. This will cause your function to run whenever the wallet's address appears anywhere on the blockchain; this includes emitted events, sent transactions, updated storage and more. Inside the function handler we simply get the wallet's health factor and send it to the function's webhook.

import { ethers } from "https://cdn.skypack.dev/[email protected]";

const providerString = "http://chain-provider";

const aaveAddress = "0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9";
const aaveAbi = [
    "function getUserAccountData(address user) view returns (uint256 totalCollateralETH, uint256 totalDebtETH, uint256 availableBorrowsETH, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor)",
];

export async function triggerHandler(context, data) {
    const walletAddress = context.trigger.address.address;
    const provider = new ethers.providers.JsonRpcProvider(providerString);

    const aavePool = new ethers.Contract(
        aaveAddress,
        aaveAbi,
        provider
    );

    const healthFactor = await aavePool
        .getUserAccountData(walletAddress, { blockTag: context.blockNumber })
        .then(
            (res) => Number(res.healthFactor.toHexString()) * Math.pow(10, -18)
        );

    return {
        context: context,
        walletAddress: walletAddress,
        healthFactor: healthFactor
    };
}

Oracle Updates Trigger

The second approach is to set a trigger on any change in the oracle's price feed. We do this by setting a storage trigger on the variable that the oracle contract uses to store its price. To do so we must:

  1. Analyze the oracle contract - we will use this contract as an example.
  2. Find the storage variable that stores the price - s_transmissions.
  3. Set the trigger on the variable name.

Once the function is triggered, the user's health factor along with the old and new prices from the oracle will be sent to the function's webhook.

Note: Since we set the trigger on the oracle contract, we include the relevant wallet address inside the function itself.

import { ethers } from "https://cdn.skypack.dev/[email protected]";

const providerString = "http://chain-provider";

const chainLinkAggregatorAddress = "0xb4c4a493AB6356497713A78FFA6c60FB53517c63";
const chainLinkAggregatorAbi = [
    "function getAnswer(uint256 _roundId) view returns (int256)",
];

const aaveAddress = "0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9";
const aaveAbi = [
    "function getUserAccountData(address user) view returns (uint256 totalCollateralETH, uint256 totalDebtETH, uint256 availableBorrowsETH, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor)",
];

export async function triggerHandler(context, data) {
    // The new rate is the last 48 characters of the final value.
    const valueLength = data.finalValue.length;
    const newRateHexString = data.finalValue.slice(valueLength - 48, valueLength);
    const newRate = Number("0x" + newRateHexString);

    // The roundId is the accessed key of s_transmissions
    const roundId = Number(data.keys[0]);
    const prevRoundId = roundId - 1;

    const provider = new ethers.providers.JsonRpcProvider(providerString);

    // Chainlink CRV/ETH aggregator contract
    const chainLinkAggregator = new ethers.Contract(
        chainLinkAggregatorAddress,
        chainLinkAggregatorAbi,
        provider,
    );

    // Read the previous round's rate through the getAnswer function.
    const prevRoundRate = await chainLinkAggregator
        .getAnswer(prevRoundId)
        .then((res) => Number(res.toHexString()));

    // AAVE v2 pool contract
    const aavePool = new ethers.Contract(
        aaveAddress,
        aaveAbi,
        provider,
    );

    const whaleAddress = "0x7a16ff8270133f063aab6c9977183d9e72835428";
    // Calculate the new health factor from AAVE
    const healthFactor = await aavePool
        .getUserAccountData(whaleAddress, { blockTag: context.blockNumber })
        .then(
            (res) => Number(res.healthFactor.toHexString()) * Math.pow(10, -18),
        );

    return {
        context: context,
        newRate: newRate,
        roundId: roundId,
        oldRate: prevRoundRate,
        healthFactor: healthFactor,
    };
}