> For the complete documentation index, see [llms.txt](https://docs.curvance.com/app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.curvance.com/app/developer-docs/quick-start-guides/borrowing-and-repayment/borrow.md).

# 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](/app/developer-docs/lending-protocol/ctoken.md) 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.

```js
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 cUSDC = new ethers.Contract(cUSDCAddress, BORROWABLE_CTOKEN_ABI, wallet);
const USDC = new ethers.Contract(USDCAddress, ERC20_ABI, wallet);

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

const centralRegistry = new ethers.Contract(
  await cUSDC.centralRegistry(),
  CENTRAL_REGISTRY_ABI,
  provider
);

const oracleManager = new ethers.Contract(
  await centralRegistry.oracleManager(),
  ORACLE_MANAGER_ABI,
  provider
);

const NO_ERROR = 0n;

function mulDivUp(a, b, denominator) {
  return (a * b + denominator - 1n) / denominator;
}

async function debtAssetValueUsdWad(amountInDebtUnits, debtDecimals) {
  const underlying = await cUSDC.asset();
  const [priceWad, errorCode] = await oracleManager.getPrice(
    underlying,
    true,
    false
  );

  if (errorCode !== NO_ERROR) {
    throw new Error("Debt asset price is unavailable");
  }

  return mulDivUp(
    amountInDebtUnits,
    priceWad,
    10n ** BigInt(debtDecimals)
  );
}

async function preflightBorrowUSDC(amount) {
  const userAddress = await wallet.getAddress();

  // USDC usually has 6 decimals, but integrations should read decimals.
  const USDCDecimals = await USDC.decimals();
  const amountInUsdcUnits = ethers.parseUnits(
    amount.toString(),
    USDCDecimals
  );

  if (amountInUsdcUnits === 0n) {
    throw new Error("Borrow amount must be greater than zero");
  }

  const [, , borrowingPaused] = await marketManager.actionsPaused(cUSDCAddress);
  if (borrowingPaused) throw new Error("Borrowing is paused");

  const debtCap = await marketManager.debtCaps(cUSDCAddress);
  if (debtCap === 0n) throw new Error("USDC is not borrowable in this market");

  const marketDebtFresh =
    await cUSDC.marketOutstandingDebtUpdated.staticCall();
  if (marketDebtFresh + amountInUsdcUnits > debtCap) {
    throw new Error("Debt cap would be exceeded");
  }

  const postedOnDebtToken = await cUSDC.collateralPosted(userAddress);
  if (postedOnDebtToken > 0n) {
    throw new Error("Cannot borrow from a cToken already posted as collateral");
  }

  const [, maxDebtUsdWad, currentDebtUsdWad] =
    await marketManager.statusOf.staticCall(userAddress);
  const borrowValueUsdWad = await debtAssetValueUsdWad(
    amountInUsdcUnits,
    USDCDecimals
  );
  const projectedDebtUsdWad = currentDebtUsdWad + borrowValueUsdWad;

  if (projectedDebtUsdWad > maxDebtUsdWad) {
    throw new Error("Insufficient collateral for this borrow");
  }

  const minLoanSizeUsdWad = await marketManager.MIN_LOAN_SIZE();
  if (projectedDebtUsdWad < minLoanSizeUsdWad) {
    throw new Error("Borrow would leave total debt below MIN_LOAN_SIZE");
  }

  return { amountInUsdcUnits, projectedDebtUsdWad, maxDebtUsdWad };
}
```

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()`

```js
async function borrowUSDC(amount) {
  const userAddress = await wallet.getAddress();
  const { amountInUsdcUnits } = await preflightBorrowUSDC(amount);

  // The receiver gets the underlying. msg.sender takes on the debt.
  const borrowTx = await cUSDC.borrow(amountInUsdcUnits, userAddress);
  const receipt = await borrowTx.wait();

  console.log(`Successfully borrowed ${amount} USDC`);
  return receipt;
}
```

**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.

```js
// userSigner owns the position. platformSigner executes the delegated borrow.
const userAddress = await userSigner.getAddress();
const yourPlatformAddress = await platformSigner.getAddress();
const USDCDecimals = await USDC.decimals();
const amountInUsdcUnits = ethers.parseUnits("100", USDCDecimals);

// The user must approve your platform on this cToken first.
const approvalTx = await cUSDC
  .connect(userSigner)
  .setDelegateApproval(yourPlatformAddress, true);
await approvalTx.wait();

// Called by your platform signer after delegation is active.
const tx = await cUSDC.connect(platformSigner).borrowFor(
  amountInUsdcUnits,
  yourPlatformAddress,
  userAddress
);
await tx.wait();
```

* `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](/app/developer-docs/plugin-and-delegation-system.md) 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.

```js
async function safeBorrowUSDC(amount) {
  try {
    return await borrowUSDC(amount);
  } catch (error) {
    const errData = error.data ?? error.info?.error?.data;

    if (errData) {
      for (const iface of [
        cUSDC.interface,
        marketManager.interface,
        oracleManager.interface
      ]) {
        try {
          const decoded = iface.parseError(errData);
          console.error(`Borrow reverted: ${decoded.name}`, decoded.args);
          return null;
        } catch (_) {
          // This interface does not define the selector.
        }
      }

      console.error("Unrecognized custom error:", errData);
    } else {
      console.error("Non-revert error:", error.message);
    }

    throw error;
  }
}
```

#### 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:

```js
const errorSelector  = (error.data ?? '').slice(0, 10); // "0x12345678"
const targetSelector = ethers.id('MarketManager__InsufficientCollateral()').slice(0, 10);

if (errorSelector === targetSelector) {
  console.error('Insufficient collateral for the requested borrow amount');
}
```

#### 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`.                             |

***


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.curvance.com/app/developer-docs/quick-start-guides/borrowing-and-repayment/borrow.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
