Sepolia

Developer Guide

Documentation

How the Particula oracle integrates with a lending protocol — from on-chain rating to collateral factor to health factor.

Architecture Overview

Particula is a cross-chain credit rating oracle. Ratings are attested on a Hub chain (LUKSO Testnet), bridged via Hyperlane to Ethereum Sepolia, and consumed by lending protocols through a lightweight oracle interface.

Attestor
Signs rating intent
LUKSO Hub
OracleIntentRegistry
Hyperlane Bridge
MinimalRatingBridge
Sepolia Oracle
MockOracleReceiver
Lending Spoke
getRatingCF() → HF

In production the OAppReceiverLZ validates EIP-712 signatures and stores ratings. In this demo, a MockOracleReceiver mirrors the same interface so the lending logic is identical.

How a Lending Protocol Fetches Ratings

Integration requires a single view call on the oracle receiver. The Spoke calls this every time it needs to compute collateral limits:

solidity
interface ILendingOracle {
    // Returns the raw rating score (0–10 000) for a given asset key.
    function getRating(string calldata key) external view returns (uint256);
}

// Inside MockLendingSpoke:
function getRatingCF(uint256 reserveId) public view returns (uint256) {
    Reserve storage r = reserves[reserveId];
    if (bytes(r.oracleKey).length == 0) return 0;

    uint256 rating = ILendingOracle(oracleReceiver).getRating(r.oracleKey);
    uint256 cf = (rating * CF_MULTIPLIER) / 10_000;  // CF_MULTIPLIER = 9000 (90%)
    return cf > r.maxCF ? r.maxCF : cf;              // capped at governance maxCF
}

The oracle key is a string identifier like "Ethereum-0xAAAA0001" that uniquely maps to an asset across chains. It is set once when the reserve is registered via addReserve().

Collateral Factor (CF)

The CF determines how much borrowing power one unit of collateral provides.

text
CF = min(maxCF, floor(rating × 9000 / 10 000))

Where:
  rating    Oracle score, range 0–10 000
             9 800 = AAA,  7 500 = BBB,  3 500 = CCC
  9000/10000 Fixed 90% scaling constant (built into protocol)
  maxCF      Governance cap — default 8 500 bps (85%)
  CF unit    Basis points (bps) — 8 500 bps = 85%
Rating 9 800 (AAA)
8 500 bps (85%)
Capped at maxCF
Rating 7 500 (BBB)
6 750 bps (67.5%)
Below cap
Rating 3 500 (CCC)
3 150 bps (31.5%)
High-risk asset

Health Factor (HF)

HF measures the safety of an open position. It is recomputed on every oracle update — no user action needed.

text
HF = weightedCollateral / totalDebt

weightedCollateral = Σ (supply_i × CF_i / 10 000)

HF < 1.0  → Position is underwater → liquidatable
HF ≥ 1.0  → Position is solvent
HF ≥ 1.1  → Healthy buffer (recommended minimum)
solidity
// Inside MockLendingSpoke:
function getHealthFactor(address user) external view returns (uint256) {
    uint256 weighted = 0;
    for (uint256 i = 0; i < reserves.length; i++) {
        uint256 supply = supplies[user][i];
        if (supply > 0) {
            uint256 cf = getRatingCF(i);       // live oracle read
            weighted += (supply * cf) / 10_000;
        }
    }
    uint256 debt = debts[user][stableReserveId];
    if (debt == 0) return type(uint256).max;   // ∞ — no debt
    return (weighted * 1e18) / debt;           // scaled 18 decimals
}

Cross-Chain Rating Flow

Each rating update triggers two writes: one directly on Sepolia (for immediate availability) and one dispatched from LUKSO Testnet via Hyperlane (mirroring the production attestor flow).

  1. 1
    Attestor (Sepolia)Calls setOracleData(key, rating, timestamp) on MockOracleReceiver. Instant — no bridge needed.
  2. 2
    Attestor (LUKSO Testnet)Calls dispatchRatingTo(receiver, key, rating) on MinimalRatingBridge. Hyperlane delivers the message cross-chain.
  3. 3
    MinimalRatingReceiver (Sepolia)Receives Hyperlane message, validates sender, calls setOracleData() on the lending oracle. Same result as step 1 via bridge.
  4. 4
    MockLendingSpoke (Sepolia)Next call to getRatingCF() / getHealthFactor() reads the new rating. CF and HF update automatically — no governance vote, no user action.

Integration Cost for a Lending Protocol

Adding Particula ratings to an existing lending protocol requires minimal changes:

Borrow flow
Add one view call: getRatingCF(reserveId). Replace hardcoded LTV with the returned value.
Health check
getHealthFactor() already calls getRatingCF() internally. No extra integration needed.
Liquidation
No change — liquidation triggers when HF < 1.0, same as before. CF drops automatically when rating drops.
Aave V4 path
Risk Steward or Edge Risk Oracle monitors the feed, calls updateDynamicReserveConfig() to adjust CF within governance-set bounds. No full DAO vote needed.

Deployed Contracts (Ethereum Sepolia)

Each scenario has isolated contracts. All addresses are in src/config/contracts.ts.

ScenarioOracleSpoke
S1 — Happy Path0xebE096a2…0xCa00c0C2…
S2 — Downgrade0xfcE6155c…0x3AC48Ce2…
S3 — Instability0xc3Ca2c71…0x06635978…
S4 — Multi-Asset0x86591253…0xb3b0A00B…
S5 — Gov CF Cap0x9de4961a…0x64ffc4f4…
S6 — Gov Delist0x363557f0…0xd8F8AA11…
S7 — Horizon RWA0xe2b4a10f…0x0931E8D4…

Full addresses in contracts.ts. Explorer: sepolia.etherscan.io

Cost Analysis — Onchain vs Offchain

Each rating update triggers both onchain and offchain operations. The table below shows representative costs per operation type based on testnet data. Offchain values are mocked — swap for real provider billing before mainnet.

OperationLayerProviderEst. costFrequency
Oracle rating writeOnchainSepolia gas~$0.002Per rating update
Hyperlane bridge dispatchOnchainLUKSO gas + Hyperlane~$0.003Per cross-chain sync
Risk data API fetchOffchainParticula API~$0.0001Per rating update
Passport metadata pinOffchainPinata IPFS~$0.0003Per metadata update
User supply/borrowOnchainSepolia gas~$0.001Per user action
LiquidationOnchainSepolia gas~$0.003Per liquidation

Optimization Recommendations

  • 1.Batch oracle updates. Combining multiple asset rating writes into one transaction reduces per-update gas by ~60% at scale (shared base tx cost).
  • 2.IPFS content-addressing. Identical metadata payloads share the same CID — skip re-pinning if the rating tier hasn't changed (e.g. AA → AA with same subscores).
  • 3.Lazy bridge dispatch. Only bridge to spoke chains when a position on that chain is active. Avoid syncing to idle chains unnecessarily.
  • 4.API call deduplication. Cache oracle API responses for up to 60 seconds — multiple downstream consumers can share one fetch when rating updates are infrequent.

Note: Offchain cost values (API, IPFS) are mocked with realistic estimates. Before mainnet, wire in real billing data from your API provider and Pinata webhook — the Cost Tracker UI is already structured to accept real values by replacing the mock generators in src/lib/mockOffchainCosts.ts.