An Aave V3 repayment amount is an input to a transaction; accrued debt is calculated from the borrower's scaled balance and the reserve's variable borrow index. A previously observed balance can be too small by execution time. For the standard V3 repayment path, distinguish the requested amount, actual underlying paid and remaining scaled debt before deciding that a loan is fully repaid.
Start with the asset, borrower and implementation
The example below follows the standard variable-debt path in Aave V3 origin v3.7.0, pinned to commit cff15de6d1271b0c800fc001f4aea4c263e8a597 and checked on 28 October 2026. A repository release does not establish which implementation a particular market has deployed. Confirm the chain, Pool and variable debt token implementations before applying its rounding rules. The scope here is V3 EVM repayment with the borrowed underlying ERC-20.
Identify the borrower separately from the payer. A repayment can reduce someone else's debt while taking tokens from the caller. In the V3 Pool interface, asset identifies the borrowed underlying, onBehalfOf identifies the borrower, and interestRateMode is 2 for variable debt. An approval transaction and a repayment transaction also perform separate jobs: authorizing a spender does not reduce debt.
Keep three amounts in the reconciliation:
- The debt observed when preparing the transaction.
- The underlying amount actually paid when repayment executes.
- The debt remaining after execution.
A wallet's rounded display cannot replace any of them. For a six-decimal asset, 1,100 tokens means 1,100,000,000 base units. The ERC-20 decimals guide explains that conversion; applying eighteen decimals out of habit would prepare a different quantity.
Follow the snapshot into a later repayment
This is a hypothetical accounting fixture using fictional underlying U with six decimals. It is not a live loan, reserve quote or interest-rate forecast. There are no new borrows, liquidations or other account changes between the two observations. The indices are stipulated to isolate accrual and integer rounding.
The borrower holds 1,000,000,000 raw scaled debt units. At the preparation block, the normalized variable borrow index is 1.100000. The observed debt is therefore 1,100.000000 U. At the later execution block, the index is 1.100110, making the pre-repayment debt 1,100.110000 U.
This growth does not require another borrowing transaction. The pinned VariableDebtToken balance getter applies the normalized variable debt index to stored scaled debt. ReserveLogic calculates that normalized index for the query timestamp, including interest since the stored reserve checkpoint. Reading a stored index alone is therefore a different operation from requesting normalized debt.
Suppose the payer submits the old exact snapshot: 1,100.000000 U. It fits the wallet balance and the token approval. Those checks establish funding and authority, but they do not make the old number equal the later debt.
| Point in this hypothetical sequence | Normalized index | Raw scaled debt | Visible debt in U |
|---|---|---|---|
| Preparation snapshot | 1.100000 | 1,000,000,000 | 1,100.000000 |
| Execution, before repayment | 1.100110 | 1,000,000,000 | 1,100.110000 |
| After paying 1,100.000000 U | 1.100110 | 99,991 | 0.110002 |
| Alternative: full payment at that execution state | 1.100110 | 0 | 0.000000 |
The last row is a separate alternative starting from the same pre-repayment position. It does not follow the preceding partial repayment. It requires an actual payment of 1,100.110000 U and sufficient funding and authorization for that amount.
The stale payment leaves 0.110002 U rather than the decimal subtraction result of 0.110000 U. That extra 0.000002 U comes from the conversion between underlying and scaled debt units in this fixture. Calling the whole remainder “new interest” would mix two causes.
Recompute the rounding in raw units
The pinned TokenMath helpers round visible variable debt up and the scaled amount burned by repayment down. These are explicit implementation rules; a generic receipt-token formula is insufficient.
Let S mean raw scaled debt, I mean the raw index and R mean the ray scale of 10^27. The two calculations are:
- Visible raw debt: round
S × I / Rup to an integer. - Raw scaled debt burned: round
payment × R / Idown to an integer.
For the stale payment, dividing 1,100,000,000 by 1.100110 and rounding down produces 999,900,009 scaled units burned. Subtracting that from 1,000,000,000 leaves 99,991 scaled units. Multiplying the remainder by 1.100110 produces 110,001.09901 raw underlying units; rounding up gives 110,002, or 0.110002 U.
The debt reduction at this fixed execution index is 1,099,999,998 raw units, while the payment is 1,100,000,000. That two-unit difference is the fixture's rounding outcome, not an additional stated repayment fee. Keep the actual transfer and the debt change as separate ledger entries. The broader lending receipt accounting guide uses the same discipline to distinguish asset flows from changes in an accounting balance.
Do not turn this result into a universal “add two units” rule. A separate hypothetical boundary case shows why: with three raw scaled debt units and an index of 1.10, visible debt rounds up to four raw underlying units. A one-unit repayment converts to zero scaled units. The scaled burn implementation rejects a zero scaled burn. A positive payment input can therefore be too small to execute at all.
What repay-all actually selects
For a direct self-repayment, the V3 Pool documentation specifies type(uint256).max for full repayment. This is a sentinel asking the Pool to select the debt at execution. It is different from typing the largest balance the interface displayed earlier.
In the pinned repayment logic, the reserve state is updated before debt is calculated. The underlying repayment is capped at that debt, and the transfer uses the resulting payback amount. In the hypothetical full-payment alternative, the amount transferred is 1,100.110000 U. The sentinel itself is not transferred.
It also does not create tokens or grant spending permission. A wallet funded with only the old 1,100.000000 U cannot cover this example's execution debt. An allowance covering only that old figure can likewise be insufficient. Review the actual token balance and spender authorization independently of the selected repayment mode. A max repayment input does not require treating unlimited token approval as the same decision.
For repayment on behalf of another borrower, the pinned repayment validation rejects the max sentinel unless payer and borrower match. Use an explicit authorized amount for that route. A larger finite input is still capped at accrued debt in the checked underlying path, but a fixed buffer cannot guarantee closure after an unbounded delay or intervening account change.
When a contract or adapter calls the Pool, trace the payer identity at that boundary. “My wallet initiated it” does not establish that the Pool sees a self-repayment. Check the exact route rather than forwarding a sentinel because it worked through a different interface.
A successful repayment can still leave a position open
A partial repayment can succeed exactly as requested. Transaction success alone does not establish zero debt. Conversely, a failed token transfer does not leave the earlier internal debt burn as a completed repayment; the transaction reverts. Separate a submitted transaction, a confirmed receipt and the resulting account state.
The checked V3 validation also requires an active, unpaused reserve, a nonzero repayment input and existing variable debt. If another transaction has already cleared the selected debt, a repeated repayment can fail instead of acting as a harmless duplicate. Re-read the position before deciding whether to retry.
Paying with aTokens follows a different path from supplying underlying from the wallet. The checked aToken repayment route burns the user's supplied asset claim. It checks health factor when that reserve is used as collateral and borrowing remains. A collateral swap route adds its own execution conditions. Neither route should inherit the underlying-payment example's assumptions without inspection.
Closing one reserve's debt also does not establish that the whole account has no borrowing. The multi-asset health factor guide explains the account-wide calculation. The liquidity rate versus borrow rate comparison separates rates on supplied assets from the cost accruing on debt; a net dashboard rate is not the repayment amount.
Prove closure with a short repayment record
Use one block reference for related preflight reads. Preserve exact integers even if the review screen shows shorter decimal amounts. A simulation can reveal a funding, allowance or validation failure at its chosen state; later execution still needs its own evidence.
| Record | Check to complete |
|---|---|
| Asset and implementation | Save chain, Pool, underlying, variable debt token, decimals and verified code version |
| Borrower and payer | Confirm whose debt changes and whose underlying is spent |
| Preparation state | Save block, scaled debt, normalized index and exact visible debt |
| Requested operation | Record finite amount or self-repayment sentinel and the actual route |
| Funding and approval | Compare underlying balance and allowance with the executable payment |
| Confirmed result | Save transaction status, actual repayment and underlying transfer evidence |
| Closure state | Read remaining scaled debt and visible debt at the confirmed block |
For an underlying repayment, match the Pool's Repay amount to the actual asset movement, then inspect the remaining debt. Avoid reconstructing closure from a rounded screen value or a single debt-token event label: the scaled-token implementation can emit a mint event during a repayment when accrued interest exceeds the amount paid.
Finish with a precise statement: “At block B, this borrower has zero scaled variable debt for this reserve,” or record the exact positive remainder. In the stale-payment fixture, that remainder is 99,991 scaled units and 0.110002 U. Keeping both figures makes the next action reproducible instead of leaving “repaid” to mean whatever the interface rounded away.
