> 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/flash-loans.md).

# Flash Loans

Curvance flash loans are available through `BorrowableCToken.flashLoan(uint256 assets, bytes data)`. A receiver contract calls the cToken, receives the cToken's underlying asset, executes its callback, and must let the cToken pull back `assets + fee` before the transaction ends.

Curvance flash loans are ERC-3156-inspired, not fully ERC-3156-compatible. The callback returns `bytes32`, but Curvance's callback signature is `onFlashLoan(uint256 assets, uint256 assetsReturned, bytes calldata data)`, not the ERC-3156 receiver signature. The cToken also does not validate the returned `bytes32`.

Flash loans are not normal borrows. `flashLoan` does not create account debt, does not check `debtCaps`, does not check account collateral, and does not call the MarketManager hold-period hooks. The amount check is based on the underlying tokens currently held by the cToken.

***

### Key Concepts

#### What is a Flash Loan?

A flash loan is an uncollateralized loan that must be repaid inside the same transaction. If the receiver does not return the borrowed assets plus the fee, the repayment pull reverts and the whole transaction is unwound.

In Curvance, the caller is also the receiver. `BorrowableCToken.flashLoan` sends the underlying to `msg.sender` and then calls `IFlashLoan(msg.sender).onFlashLoan(...)`. There is no separate receiver argument.

#### Flash Loan Fee

* **Fee rate:** `FLASHLOAN_FEE = 4`, with `BPS = 1e4`, so the fee is 4 basis points, or 0.04%.
* **Rounding:** `flashFee(assets)` computes `ceil(assets * 4 / 10_000)`. For any nonzero flash loan, the fee is at least one smallest unit of the underlying asset, for example 1 wei for WETH or 1 micro-USDC for USDC.
* **Where the fee goes:** after repayment, the cToken adds the fee to `_totalAssets`. That increases the assets backing cToken shares, so flash-loan fees accrue to lenders.
* **No hold-period effect:** `flashLoan` does not set or check the 20-minute `MIN_HOLD_PERIOD`. If your callback separately borrows, posts collateral, repays, redeems, withdraws, or transfers cTokens, those calls still follow their normal Curvance checks.

***

### Implementation Steps

#### Receiver Contract

The repository's `IBorrowableCToken` interface does not declare `flashLoan` or `flashFee`. For a Solidity receiver, define a small local cToken interface for the flash-loan surface, or import the concrete `BorrowableCToken` type if that fits your project.

Your contract must implement `onFlashLoan()` to receive the callback:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
import { IFlashLoan } from "contracts/interfaces/IFlashLoan.sol";

interface ICurvanceFlashCToken {
    function asset() external view returns (address);
    function flashFee(uint256 assets) external view returns (uint256);
    function flashLoan(uint256 assets, bytes calldata data) external;
}

contract MyFlashBorrower is IFlashLoan {
    using SafeTransferLib for address;

    error UnauthorizedCallback();

    ICurvanceFlashCToken public immutable cToken;
    address public immutable asset;

    constructor(address cToken_) {
        cToken = ICurvanceFlashCToken(cToken_);
        asset = cToken.asset();
    }

    function executeFlashLoan(uint256 assets, bytes calldata data) external {
        cToken.flashLoan(assets, data);
    }

    /// @inheritdoc IFlashLoan
    function flashFee(uint256 assets) external view returns (uint256) {
        return cToken.flashFee(assets);
    }

    /// @inheritdoc IFlashLoan
    function onFlashLoan(
        uint256 assets,
        uint256 assetsReturned,
        bytes calldata data
    ) external returns (bytes32) {
        if (msg.sender != address(cToken)) revert UnauthorizedCallback();

        // Decode `data`, execute your strategy, and make sure this contract
        // ends with enough underlying to repay `assetsReturned`.

        // The cToken pulls `assetsReturned` from this contract immediately
        // after this callback returns.
        asset.safeApprove(address(cToken), assetsReturned);

        // Curvance does not validate this value, but returning the common
        // ERC-3156 magic value keeps the receiver easy to inspect.
        return keccak256("ERC3156FlashBorrower.onFlashLoan");
    }
}
```

{% hint style="info" %}
**Note:** `IFlashLoan` declares both `flashFee` and `onFlashLoan`. Curvance's cToken only invokes `onFlashLoan` on your contract; `flashFee` is declared for ERC-3156 parity. If you implement `IFlashLoan` and don't want to duplicate the math, delegate to the cToken's own `flashFee` as shown above.
{% endhint %}

{% hint style="success" %}
The receiver must have enough underlying to cover the fee. The flash loan gives the receiver `assets`; the extra fee must come from profit made during the callback or from funds already held by the receiver.
{% endhint %}

### Calling `flashLoan()`

Use placeholder addresses in examples and source real values from the deployment registry for the chain you're on.

Call `flashLoan(uint256 assets, bytes calldata data)` on the cToken:

```solidity
import { ethers } from "ethers";

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

const cTokenAddress = "0x...";
const flashBorrowerAddress = "0x...";

const FLASH_CTOKEN_ABI = [
  "function asset() view returns (address)",
  "function flashFee(uint256 assets) view returns (uint256)",
  "function flashLoan(uint256 assets, bytes data)",
  "event Flashloan(uint256 assets, uint256 assetsFee, address account)"
];

const ERC20_ABI = [
  "function balanceOf(address account) view returns (uint256)",
  "function decimals() view returns (uint8)"
];

const FLASH_BORROWER_ABI = [
  "function executeFlashLoan(uint256 assets, bytes data)"
];

const cToken = new ethers.Contract(cTokenAddress, FLASH_CTOKEN_ABI, wallet);
const flashBorrower = new ethers.Contract(
  flashBorrowerAddress,
  FLASH_BORROWER_ABI,
  wallet
);

async function quoteFlashLoan(assets) {
  if (assets === 0n) throw new Error("Flash-loan amount must be greater than zero");

  const assetAddress = await cToken.asset();
  const underlying = new ethers.Contract(assetAddress, ERC20_ABI, provider);

  const [available, fee, decimals] = await Promise.all([
    underlying.balanceOf(cTokenAddress),
    cToken.flashFee(assets),
    underlying.decimals()
  ]);

  if (assets > available) {
    throw new Error("Requested flash loan exceeds cToken underlying balance");
  }

  return {
    assetAddress,
    available,
    fee,
    totalRepayment: assets + fee,
    decimals
  };
}

async function runFlashLoan(assets, data = "0x") {
  const quote = await quoteFlashLoan(assets);

  console.log(
    `Fee: ${ethers.formatUnits(quote.fee, quote.decimals)} underlying`
  );

  const tx = await flashBorrower.executeFlashLoan(assets, data);
  const receipt = await tx.wait();

  return receipt;
}
```

Use the underlying token's `balanceOf(cTokenAddress)` for exact flash-loan availability. `assetsHeld()` is useful for borrow-market accounting, but it is not the `flashLoan` limit because it subtracts outstanding debt and the base reserve. The flash-loan implementation checks the cToken's raw underlying balance.

***

### Flash Loan Execution Flow

Inside `BorrowableCToken.flashLoan`, execution proceeds in this order:

1. Accrue pending interest with `_accrueIfNeeded()`.
2. Revert on `assets == 0`.
3. Check `assets <= asset.balanceOf(address(this))`.
4. Compute `fee = flashFee(assets)` and `assetsReturned = assets + fee`.
5. Transfer `assets` of the underlying token to `msg.sender`.
6. Call `IFlashLoan(msg.sender).onFlashLoan(assets, assetsReturned, data)`.
7. Pull `assetsReturned` from `msg.sender` with `safeTransferFrom`.
8. Add `fee` to `_totalAssets`.
9. Emit `Flashloan(assets, fee, msg.sender)`.

***

### Important Considerations

#### 1. Available liquidity

Before initiating a flash loan, ensure the cToken holds enough underlying:

```solidity
uint256 availableLiquidity = IBorrowableCToken(borrowableCToken).assetsHeld();
require(loanAmount <= availableLiquidity, "Insufficient liquidity");
```

#### 2. Fee Calculation

Always query the fee from the contract; don't recompute it off-chain. The on-chain implementation rounds up:

```solidity
uint256 fee           = IBorrowableCToken(borrowableCToken).flashFee(loanAmount);
uint256 totalRepayment = loanAmount + fee;
```

#### 3. Token Approval

Your contract must have approved the cToken to pull `assets + fee` by the time `onFlashLoan` returns. The simplest option is to approve inside the callback:

```solidity
IERC20(asset).approve(borrowableCToken, assetsReturned);
// or, using Solady's SafeTransferLib to avoid the race-condition variants:
SafeTransferLib.safeApprove(asset, borrowableCToken, assetsReturned);
```

If you fail to approve, or your contract's balance is insufficient at callback-end, the `safeTransferFrom` pull inside `flashLoan` reverts and the entire transaction is unwound.

#### 4. Callback-spoofing protection

Any address can call `onFlashLoan` on your contract; the require-check on `msg.sender == borrowableCToken` is load-bearing. Without it, an attacker can trigger your business logic with attacker-controlled `assets` and `data`. This is the single most common flash-loan receiver mistake.

#### 5. Reentrancy

`flashLoan` itself is not `nonReentrant`, but every deposit, withdraw, redeem, borrow, repay, and collateral entrypoint on the cToken is. Nested flash loans on the same cToken may be possible; nested deposits / redeems / borrows / repays from your callback revert with the reentrancy lock. Design flows to avoid reentering the cToken during the callback.

#### 6. Return Value

Curvance does not validate the callback's returned `bytes32`. Returning the ERC-3156 magic value, returning another `bytes32`, or returning `bytes32(0)` does not change Curvance's repayment logic. Reverting from the callback still reverts the whole flash loan.

***

### Error Handling

`flashLoan` itself is not marked `nonReentrant`. Your callback is therefore not automatically inside the cToken's reentrancy lock. However, deposit, mint, withdraw, redeem, borrow, repay, collateral, liquidation, and several admin entrypoints are `nonReentrant`; once your callback enters one of those functions, that function's normal reentrancy lock applies.

Treat callback-side Curvance calls as advanced flows. During the callback, the cToken's raw underlying balance is temporarily lower by the flash-loaned amount, while repayment has not happened yet. Any nested action still runs its own pause, liquidity, collateral, hold-period, and transfer checks.

| Error                                        | Meaning                                                                                     |
| -------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `BaseCToken__ZeroAmount()`                   | `flashLoan(0, data)` was called.                                                            |
| `BorrowableCToken__InsufficientAssetsHeld()` | Requested `assets` exceeded the cToken's raw underlying token balance.                      |
| `BorrowableCToken__DepositsNotInitialized()` | The cToken has not been initialized and accrual reached the `assetsHeld()` path.            |
| `TransferFailed()`                           | The outgoing underlying transfer from the cToken to the receiver failed.                    |
| `TransferFromFailed()`                       | The repayment pull failed, usually because the receiver's allowance or balance was too low. |
| `ApproveFailed()`                            | Your receiver's callback attempted a safe approval and the underlying token rejected it.    |

For ethers v6 integrations, use the `iface.parseError(errData)` pattern from Borrow: Error Handling. Include the cToken ABI, your receiver ABI, and small ABI fragments for Solady transfer errors if your generated ABI does not include them.

***


---

# 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/flash-loans.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.
