For the complete documentation index, see llms.txt. This page is also available as Markdown.

Borrow

When you borrow an asset through Curvance, you're creating a debt position against your collateral in the same isolated market. The system evaluates your position before every borrow and during every liquidity check to ensure it remains healthy (above the required collateralization ratio).

The borrowing process is straightforward: you specify how much underlying you want to borrow and the recipient wallet, and the cToken contract handles the debt issuance directly. The borrowed underlying is sent to the receiver address; no cToken shares are minted to you for the debt. Debt is tracked in a separate internal mapping on the cToken. See Introduction to cTokens for the full lending-vs-debt model.

Use fresh or projected reads for borrow UX. marketManager.statusOf.staticCall(account) gives the account's current collateral, maximum debt, and current debt in USD WAD terms. The transaction remains the source of truth because the borrow path accrues interest and rechecks caps, collateral, price availability, and pool liquidity on-chain.


Before calling borrow()

A borrow reverts if any of the following hold:

  • Asset isn't borrowable in this market: debtCap == 0. Check with await marketManager.debtCaps(cUSDC) > 0n.

  • Borrowing paused for this cToken: inspect the 3-tuple from marketManager.actionsPaused(cUSDC).

  • Debt cap would be exceeded: marketOutstandingDebt() is a last-accrued storage read. For a fresher cap check, use await cUSDC.marketOutstandingDebtUpdated.staticCall() and compare the projected market debt to await marketManager.debtCaps(cUSDCAddress).

  • Insufficient collateral: the protocol simulates the post-borrow position. If the account would have a liquidity deficit, the call reverts with MarketManager__InsufficientCollateral().

  • Below MIN_LOAN_SIZE: each market has an immutable minimum active loan size, declared as MIN_LOAN_SIZE and configured between $10 and $100 in USD WAD. The runtime check compares the account's post-borrow USD-denominated debt to that floor, not the debt token's native units.

  • You have collateral posted on this same cToken: BorrowableCToken__CollateralPositionActive reverts if collateralPosted[owner] > 0 on the cToken you're trying to borrow from (BorrowableCToken.sol:550). Typical flows borrow the opposite side of the isolated market (e.g., post ezETH as collateral in ezETH | WETH, borrow WETH from cWETH).

  • The cToken does not hold enough underlying: if the pool cannot send the requested amount, the cToken reverts with BorrowableCToken__InsufficientAssetsHeld().

  • Amount is zero: borrow(0, receiver) reverts with BaseCToken__ZeroAmount().

  • A required price read fails: price-dependent checks can revert before the borrow completes.

Pre-Flight Check

This example uses ethers v6, placeholder addresses, and plain ethers.Contract instances. Source real addresses from the deployment registry for the chain you're on.

The MIN_LOAN_SIZE check above prices the projected debt into USD WAD before comparing. Comparing amountInUsdcUnits directly to MIN_LOAN_SIZE is a unit bug because USDC amounts are usually 1e6-scaled while MIN_LOAN_SIZE is 1e18-scaled USD value.


Calling borrow()

After a successful borrow:

  • Your account's debt in cUSDC increases by amountInUsdcUnits. Read fresh via await cUSDC.debtBalanceUpdated.staticCall(userAddress).

  • A 20-minute MIN_HOLD_PERIOD window starts. During this window, repayment, redemption / withdrawal, and share transfers for that account revert with MarketManager__MinimumHoldPeriod(). Plain deposits are still allowed and do not reset the cooldown. New borrows and new collateral postings are allowed if otherwise valid, and they reset the cooldown timestamp.

  • Debt accrues lazily. The DynamicIRM computes per-second borrow rates from utilization, and its rate adjustment interval is 10 minutes.

borrowFor (platforms borrowing on behalf of users)

Platforms that borrow on behalf of users can use borrowFor(assets, receiver, owner). The owner takes on the debt; the receiver gets the borrowed underlying. They can be different addresses.

  • owner is the account that takes on the debt; receiver is where the borrowed underlying lands. They can differ.

  • Authorization switches from a caller check to _checkDelegate(owner, msg.sender); no ERC20 allowance is involved.

  • See Plugin & Delegation for the full delegation model.


Error Handling

Curvance uses Solidity custom errors. In ethers v6, let each relevant contract interface try to parse the revert data.

Manual selector comparison (fallback)

If you can't use an Interface (e.g., no ABI to hand), compare selectors directly. Make sure both sides are normalized:

Common error scenarios

Error
Meaning

BaseCToken__ZeroAmount()

Borrow amount is zero.

MarketManager__Paused()

Borrowing is paused for this cToken.

MarketManager__CapReached()

debtCap == 0 or projected market debt would exceed the cap.

MarketManager__InsufficientCollateral()

Borrow would push the account below its collateralization requirement.

LiquidityManager__InsufficientLoanSize()

Resulting total USD-denominated debt would be below MIN_LOAN_SIZE.

LiquidityManager__PriceError() / OracleManager__ErrorCodeFlagged()

A required price read failed or was not acceptable for the borrow check.

BorrowableCToken__CollateralPositionActive()

The owner has collateral posted on the same cToken they are trying to borrow from.

BorrowableCToken__InsufficientAssetsHeld()

The cToken pool does not hold enough underlying to send the borrow.

BorrowableCToken__DepositsNotInitialized()

The market has not initialized deposits, so borrowable liquidity cannot be calculated.

PluginDelegable__Unauthorized()

borrowFor caller is not an approved delegate of owner.


Last updated

Was this helpful?