Skip to main content

Check Loans Workflow

The check-loans workflow monitors the health of all active loans and triggers liquidation for positions that become undercollateralized or expire past maturity.

Execution Flow​

1. Fetch all active loans from server
2. Read current ETH/USD price from Chainlink price feed
3. For each loan, compute health factor
4. Collect loans that are undercollateralized or past maturity
5. Submit liquidation request for unhealthy loans

Health Factor Calculation​

For each active loan:

const collateralValueUSD = Number(loan.collateralAmount) * ethPrice;
const health = collateralValueUSD / Number(loan.principal);

The health factor represents how many times the collateral covers the loan principal in USD terms.

Liquidation Conditions​

A loan qualifies for liquidation if either condition is met:

ConditionFormulaDescription
Undercollateralizedhealth < liquidationThresholdCollateral value dropped below 1.5x principal
Expirednow > loan.maturityLoan has passed its maturity date without repayment

The liquidationThreshold is currently set to 1.5 (matching the collateral system's minimum healthy ratio).

Price Feed Integration​

The ETH/USD price is read from Chainlink Data Streams on Arbitrum via the CRE's EVMClient:

const result = EVMClient.readContract(runtime, {
chainName: config.feedChainName,
contractAddress: config.ethUsdFeed,
abi: [
{
name: "latestRoundData",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [
{ name: "roundId", type: "uint80" },
{ name: "answer", type: "int256" },
{ name: "startedAt", type: "uint256" },
{ name: "updatedAt", type: "uint256" },
{ name: "answeredInRound", type: "uint80" },
],
},
],
functionName: "latestRoundData",
args: [],
});

const [, answer] = result;
const ethPrice = Number(answer) / 1e8; // 8 decimal precision

Using Chainlink's own price infrastructure ensures consistency. The same price feed that DeFi protocols rely on for billions in TVL protects GHOST's collateral valuations.

Liquidation Submission​

When unhealthy loans are found, the workflow submits their IDs to the server:

if (toLiquidate.length > 0) {
ConfidentialHTTPClient.sendRequest(runtime, {
url: `${config.ghostApiUrl}/api/v1/internal/liquidate-loans`,
method: "POST",
headers: {
"x-api-key": config.internalApiKey,
"Content-Type": "application/json",
},
body: JSON.stringify({ loanIds: toLiquidate }),
}).result();
}

The server then processes the liquidation (marking defaults, distributing collateral, downgrading credit tiers) and queues the resulting transfers for the execute-transfers workflow.

Example Scenario​

Consider a loan:

  • Principal: 10,000 gUSD
  • Collateral: 6 gETH
  • ETH price at creation: $2,500 (health = 1.5)

If ETH drops to $2,400:

health = (6 * 2400) / 10000 = 1.44

Since 1.44 < 1.5, this loan is liquidated on the next check cycle.

Configuration​

ParameterDescriptionDefault
scheduleCron intervalEvery 60 seconds
ghostApiUrlGHOST API base URLRequired
internalApiKeyAPI key for internal endpointsDON Secret
feedChainNameChain name for price feed readsethereum-testnet-sepolia-arbitrum-1
ethUsdFeedChainlink ETH/USD feed contract addressConfig
liquidationThresholdMinimum health factor1.5

Monitoring Frequency​

The 60 second interval balances responsiveness with CRE execution costs:

  • Fast enough to catch most undercollateralization events before they become severely underwater
  • Slow enough to stay within CRE execution budgets
  • In production, this interval could be tightened during high volatility periods using adaptive scheduling