First lending deposit: risk parameters and health factor
Why lending protocols matter early in the learning path
Lending is where DeFi starts to feel like actual finance. You deposit an asset, the protocol lends it out, and you earn yield from borrowers paying interest. But unlike a savings account, there is no insurance, no customer support, and no undo button. The protocol’s risk parameters are your only safety net, and understanding them before depositing is not optional.
I chose Aave V3 on Arbitrum for this first deposit. It is the most battle-tested lending protocol, the Arbitrum deployment keeps gas costs low for experimentation, and the risk parameters are well-documented. Aave V3 docs
How lending protocols actually work
When you supply an asset to Aave, you receive aTokens in return—interest-bearing tokens that represent your deposit plus accrued interest. Your aUSDC balance grows every block as borrowers pay interest. When you withdraw, you burn aTokens and get back your original asset plus whatever interest accumulated.
The protocol pools deposits from all suppliers and lends them to borrowers who post collateral. The interest rate is algorithmic: it goes up when utilization (borrowed / total supplied) is high to attract more suppliers, and goes down when utilization is low. There is no fixed term. You can withdraw anytime as long as there is available liquidity in the pool.
flowchart LR
S["Supplier"] -->|"Deposits USDC"| P["Aave Pool"]
P -->|"Mints aUSDC"| S
B["Borrower"] -->|"Posts ETH collateral"| P
P -->|"Lends USDC"| B
B -->|"Pays interest"| P
P -->|"Interest accrues to aUSDC"| S
The risk parameters that matter
Every asset on Aave has a risk configuration that controls how it can be used as collateral. These numbers are set by Aave governance and vary by asset and chain. Here are the ones you need to understand before depositing anything.
Loan-to-Value (LTV)
LTV is the maximum percentage of your collateral’s value that you can borrow against. If ETH has an LTV of 80%, you can borrow up to $800 worth of stablecoins against $1,000 worth of ETH. This is the ceiling, not a target—borrowing at max LTV leaves zero margin for price drops.
Liquidation threshold
This is the point where your position becomes eligible for liquidation. For ETH on Aave V3 Arbitrum, the liquidation threshold is 82.5%. If your borrowed amount exceeds 82.5% of your collateral value, anyone can liquidate part of your position. The gap between LTV (80%) and liquidation threshold (82.5%) is your buffer zone—it is intentionally thin to keep the protocol solvent. Aave liquidation docs
Liquidation penalty
When you get liquidated, the liquidator gets a bonus (usually 5-10% depending on the asset). This means you lose more than just the amount needed to bring your position back to health. On Aave V3, the liquidation penalty for ETH is 5%. Getting liquidated on a $1,000 position could cost you $50 on top of whatever the market already took.
Health factor
Health factor is the single number that tells you how close you are to liquidation:
Health Factor = (Collateral × Liquidation Threshold) / Total Borrows
- Above 1.0: safe.
- At 1.0: liquidation eligible.
- Below 1.0: you are actively being liquidated.
If you supply $1,000 of ETH (liquidation threshold 82.5%) and borrow $400 of USDC:
HF = (1000 × 0.825) / 400 = 2.0625
That is a comfortable buffer. If ETH drops 50%, your collateral is now $500:
HF = (500 × 0.825) / 400 = 1.03
One more percent down and you are getting liquidated.
Rate modes: variable vs stable
Aave V3 offers two interest rate modes for borrowers:
- Variable rate: fluctuates with pool utilization. When lots of people are borrowing, the rate spikes. When utilization drops, so does your rate. This is the default and what most people use.
- Stable rate: locks in a rate at the time of borrowing, but Aave governance can rebalance it if market conditions change dramatically. It is not truly fixed—it is more like “stable until it is not.” In practice, stable rates are often higher than variable rates because you are paying a premium for predictability.
For a first deposit where I am only supplying (not borrowing), the rate mode does not apply directly. But understanding it matters because the supply APY you earn comes from what borrowers pay. High variable borrow rates mean higher supply yields.
What I actually did
The deposit
I supplied 200 USDC to Aave V3 on Arbitrum. USDC is a straightforward choice for a first deposit because its value does not fluctuate against borrowed stablecoins, so there is no liquidation risk if I am only supplying and not borrowing.
Steps:
- Connected wallet to the Aave V3 Arbitrum interface at app.aave.com.
- Selected USDC and clicked Supply.
- Approved the USDC spend (I set the approval to exactly 200 USDC, not unlimited—old habit from the approval management post).
- Confirmed the supply transaction. Gas was around $0.03 on Arbitrum.
- Verified the aUSDC balance appeared in my wallet.
The supply APY at the time was around 3.8% variable. Not spectacular, but this is about learning the mechanics, not chasing yield.
Checking position on-chain
I wanted to verify the deposit independently of the Aave UI. Aave V3’s Pool contract has a getUserAccountData function that returns all the key numbers in one call:
from web3 import Web3
rpc = Web3(Web3.HTTPProvider("https://arb1.arbitrum.io/rpc"))
# Aave V3 Pool on Arbitrum
POOL = "0x794a61358D6845594F94dc1DB02A252b5b4814aD"
pool_abi = [
{
"inputs": [{"name": "user", "type": "address"}],
"name": "getUserAccountData",
"outputs": [
{"name": "totalCollateralBase", "type": "uint256"},
{"name": "totalDebtBase", "type": "uint256"},
{"name": "availableBorrowsBase", "type": "uint256"},
{"name": "currentLiquidationThreshold", "type": "uint256"},
{"name": "ltv", "type": "uint256"},
{"name": "healthFactor", "type": "uint256"},
],
"stateMutability": "view",
"type": "function",
}
]
pool = rpc.eth.contract(address=POOL, abi=pool_abi)
wallet = "0xYourWalletAddress"
data = pool.functions.getUserAccountData(wallet).call()
collateral = data[0] / 1e8 # Values are in base currency (USD) with 8 decimals
debt = data[1] / 1e8
available_borrow = data[2] / 1e8
liq_threshold = data[3] / 1e4 # Basis points
ltv = data[4] / 1e4
health_factor = data[5] / 1e18 # 18 decimals, max uint if no debt
print(f"Collateral: ${collateral:.2f}")
print(f"Debt: ${debt:.2f}")
print(f"Available to borrow: ${available_borrow:.2f}")
print(f"Liquidation threshold: {liq_threshold:.2%}")
print(f"LTV: {ltv:.2%}")
print(f"Health factor: {health_factor:.4f}" if debt > 0 else "Health factor: ∞ (no debt)")
With only a supply position and no borrows, the health factor comes back as the max uint256 value—effectively infinite. Debt is zero, available borrows reflect the LTV against my collateral.
Monitoring health factor with a script
Even though I am not borrowing yet (that is the next post), I built a simple monitoring script now. When I do borrow, I want alerting already in place rather than scrambling to set it up after the fact.
import time
import sys
from web3 import Web3
rpc = Web3(Web3.HTTPProvider("https://arb1.arbitrum.io/rpc"))
POOL = "0x794a61358D6845594F94dc1DB02A252b5b4814aD"
pool_abi = [
{
"inputs": [{"name": "user", "type": "address"}],
"name": "getUserAccountData",
"outputs": [
{"name": "totalCollateralBase", "type": "uint256"},
{"name": "totalDebtBase", "type": "uint256"},
{"name": "availableBorrowsBase", "type": "uint256"},
{"name": "currentLiquidationThreshold", "type": "uint256"},
{"name": "ltv", "type": "uint256"},
{"name": "healthFactor", "type": "uint256"},
],
"stateMutability": "view",
"type": "function",
}
]
pool = rpc.eth.contract(address=POOL, abi=pool_abi)
WALLET = "0xYourWalletAddress"
HF_WARNING = 1.5 # Print warning below this
HF_CRITICAL = 1.2 # Exit with alarm below this
POLL_SECONDS = 60
def check_health():
data = pool.functions.getUserAccountData(WALLET).call()
debt = data[1] / 1e8
if debt == 0:
return None # No debt means no liquidation risk
return data[5] / 1e18
while True:
try:
hf = check_health()
if hf is None:
print(f"[{time.strftime('%H:%M:%S')}] No active borrows — health factor is infinite")
elif hf < HF_CRITICAL:
print(f"[{time.strftime('%H:%M:%S')}] CRITICAL: Health factor {hf:.4f} — liquidation imminent")
sys.exit(1) # Hook this into whatever alerting you use
elif hf < HF_WARNING:
print(f"[{time.strftime('%H:%M:%S')}] WARNING: Health factor {hf:.4f} — consider repaying or adding collateral")
else:
print(f"[{time.strftime('%H:%M:%S')}] OK: Health factor {hf:.4f}")
except Exception as e:
print(f"[{time.strftime('%H:%M:%S')}] RPC error: {e}")
time.sleep(POLL_SECONDS)
This is intentionally simple. A production version would push alerts to Telegram or Discord, handle RPC failover, and track historical readings. But for learning, a terminal script that polls every 60 seconds and prints a status line is enough to build the habit of watching your position.
Risk analysis
Smart contract risk
Aave V3 is one of the most audited protocols in DeFi, with audits from Trail of Bits, SigmaPrime, and others. The Arbitrum deployment shares the same codebase as other chains. But “audited” does not mean “risk-free.” The protocol has had close calls from governance proposals and oracle edge cases. Aave security page
Oracle risk
Aave uses Chainlink price feeds to value collateral. If a Chainlink feed goes stale or reports an incorrect price, liquidations can happen at wrong prices or fail to trigger when they should. Aave has circuit breakers that pause markets if prices deviate too far, but there is always a window of vulnerability during extreme events.
Liquidity risk
If utilization reaches 100% (all supplied assets are borrowed), you cannot withdraw until borrowers repay or new suppliers add liquidity. The interest rate model is designed to prevent this by spiking rates at high utilization, but it has happened briefly during market panics. For a $200 deposit this is not going to ruin your day, but it matters at scale.
Governance risk
Aave’s risk parameters are set by governance votes. A proposal could change LTVs, liquidation thresholds, or reserve factors. These changes affect your position’s risk profile without any action on your part. Following governance proposals on Aave governance is part of the monitoring job.
What I am not risking here
By supplying USDC and not borrowing, I am avoiding liquidation risk entirely. The main risks are smart contract failure and temporary withdrawal illiquidity. This is deliberate—learn the deposit mechanics first, then add borrowing complexity in the next step.
What I learned
The gap between LTV and liquidation threshold is your only margin of error. For ETH on Aave V3 Arbitrum, that gap is 2.5 percentage points. Borrowing at max LTV is borrowing with almost no room for price movement.
Health factor is the one number to watch. Everything else—LTV, collateral value, debt—feeds into it. If you only track one metric, track health factor.
Supply-only is a valid first step. You earn yield, learn the interface, and verify your on-chain monitoring works—all without liquidation risk. There is no reason to rush into borrowing.
Rate mode matters less than you think for small positions. The difference between variable and stable rates on a $200 borrow is cents per year. Pick variable, move on, revisit rate strategy when the position size justifies the analysis.
Build monitoring before you need it. The health factor script took 10 minutes to write. Setting it up after you are already nervous about a position is the wrong time to debug RPC connections.
Next up: a small borrow-and-repay loop to see liquidation math and oracle dependencies firsthand.