Repaying Debt

When you're ready to reduce or eliminate debt, repay through the same BorrowableCToken that tracks the debt. This page uses cUSDC in a WETH | USDC or WBTC | USDC isolated market as the example; the same pattern applies to any borrowable cToken. Source real addresses from the deployment registry for the chain you're on.

Repayment requires the payer to approve the cToken to spend the underlying asset, then call repay(assets) for their own debt or repayFor(assets, owner) to pay another account's debt.


Before calling repay()

A repay reverts if any of these hold:

  • The debt owner is inside the 20-minute hold period: posting collateral or borrowing starts MIN_HOLD_PERIOD = 20 minutes. During that window, canRepayWithReview calls _checkHoldPeriod(owner) and reverts with MarketManager__MinimumHoldPeriod(). This applies to the debt owner, not necessarily the payer, so it also gates repayFor.

  • No outstanding debt: repay(0) and repayFor(0, owner) resolve assets to the owner's full debt. If that debt is zero, _checkZeroAmount reverts with BaseCToken__ZeroAmount(). If a nonzero repayment amount is submitted for an owner with zero debt, the cToken reverts with BorrowableCToken__InvalidParameter().

  • The amount exceeds the owner's debt: after interest accrues, _repay checks assets > debtOf and reverts with BorrowableCToken__InvalidParameter() for overpayment.

  • A partial repay would leave dust below MIN_LOAN_SIZE: full repayment skips the loan-size price check because newNetDebt == 0. Partial repayment must leave an active debt amount large enough to pass the MarketManager's USD-denominated MIN_LOAN_SIZE check.

  • A required price read fails for partial repayment: partial repayment uses the debt asset price to validate the remaining loan size. Full repayment skips that price path after the hold-period and active-position checks pass.

  • Approval or balance is too small: the cToken pulls underlying with SafeTransferLib.safeTransferFrom. If the payer's allowance or balance is insufficient, the pull reverts with TransferFromFailed().


Setup

These examples use ethers v6, placeholder addresses, and plain ethers.Contract instances.

import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider("YOUR_RPC_URL");
const wallet = new ethers.Wallet("YOUR_PRIVATE_KEY", provider);

const cUSDCAddress = "0x...";
const USDCAddress = "0x...";
const protocolReaderAddress = "0x...";

const cUSDC = new ethers.Contract(cUSDCAddress, BORROWABLE_CTOKEN_ABI, wallet);
const USDC = new ethers.Contract(USDCAddress, ERC20_ABI, wallet);

const protocolReader = new ethers.Contract(
  protocolReaderAddress,
  PROTOCOL_READER_ABI,
  provider
);

const marketManager = new ethers.Contract(
  await cUSDC.marketManager(),
  MARKET_MANAGER_ABI,
  provider
);

Helper Functions

Use projected debt for full-repay approvals. The repay transaction accrues interest before computing the pull amount, so approving only the last-accrued debt can be too low by the time repay(0) executes.

The buffer is a UX guard for ordinary accrual drift between approval and repayment. It is still not a protocol guarantee. For the simplest full-repay flow, approve ethers.MaxUint256 once if that matches your risk model.


Repaying Your Own Debt

If a user intends to fully close the position, prefer repayUSDCDebt("full") over passing a quoted debt amount. Passing the last-read debt as a nonzero amount can leave a small residual after interest accrues, and that residual can fail the MIN_LOAN_SIZE check.

The repay(0) shortcut

Curvance treats repay(0) as "repay the entire outstanding debt." Internally, _repay accrues interest, reads the owner's fresh debt with debtBalance(owner), replaces assets == 0 with that debt, and pulls exactly that amount from the payer

This is the recommended pattern when the user wants to fully exit a debt position:

  • The cToken accrues interest before computing the repayment amount.

  • The payer still needs underlying approval covering the freshly accrued debt. Use a projected debt approval, a small buffer, or a max approval.

  • The full-repay path passes newNetDebt == 0 into canRepayWithReview, so it skips the residual loan-size price check.


Repaying Debt on Behalf of Others (repayFor)

Anyone can repay anyone else's debt without delegation or permission from the borrower. repayFor(assets, owner) pulls underlying from msg.sender and applies it to owner's debt.

Use cases:

  • Helping a friend avoid liquidation.

  • Treasury management across multiple wallets in a DAO or organization.

  • Liquidation-avoidance automation or third-party insurance services.

  • Complex DeFi strategies that unwind positions on behalf of users.

Hold-period note: repayFor checks the owner's cooldown, not the payer's. If the target account posted collateral or borrowed in the last 20 minutes, repayFor reverts with MarketManager__MinimumHoldPeriod() even when the payer is a separate account. This is a protocol check against flash-loan and multi-block manipulation, not a permission check.


Common error scenarios

Error
Meaning

MarketManager__MinimumHoldPeriod()

The debt owner is inside the 20-minute cooldown after posting collateral or borrowing.

BaseCToken__ZeroAmount()

repay(0) or repayFor(0, owner) resolved to zero because the owner has no outstanding debt.

BorrowableCToken__InvalidParameter()

Nonzero repayment amount exceeds the owner's freshly accrued debt, or the repayment input is otherwise invalid for the cToken.

LiquidityManager__InsufficientLoanSize()

Partial repayment would leave remaining debt below MIN_LOAN_SIZE. Use full repayment or choose a smaller partial repayment.

MarketManager__PriceError()

A price read required for the partial-repay residual loan-size check failed. Full repayment skips this price check after the hold-period and active-position checks pass.

TransferFromFailed()

The payer's underlying transfer failed, usually because allowance or balance is insufficient.

For the recommended ethers v6 revert-decoding pattern using iface.parseError, see Borrow: Error Handling.


Last updated

Was this helpful?