> This is part 1 of 2 of the full documentation (pages 1–100 of 114).
> The content is paginated: fetch every part to see all of it.
> Next part: https://docs.curvance.com/app/llms-full.txt/1
> Page index: https://docs.curvance.com/app/llms.txt

# Click Less, Earn More

For capital that works, not capital that waits.

## Introduction

Curvance delivers lending optimized for capital efficiency. The protocol is built to support high leverage without compromising lender security by combining productive collateral mechanics with a liquidation framework engineered for speed.

By reducing liquidation costs to a fraction of legacy protocols and enabling MEV recapture, Curvance can scale lending safely while maintaining high utilization and faster position response under stress.

### What Sets Curvance Apart

Traditional lending systems degrade efficiency as risk rises. Curvance takes the opposite approach. The protocol is designed to process risk faster, allowing it to safely support higher leverage.

#### Productive Collateral

Deposit bluechips, LSTs, stablecoins, vault tokens, and more. Continue earning external yield while using them as collateral for loans.

#### High-Capacity LTVs

Borrow up to 97.5% against select assets. Elevated LTVs are made possible by our liquidation architecture and real-time auction system.

#### Hyper-Efficient Liquidation Engine

Curvance routes liquidation order flow through an auction mechanism that captures MEV and bundles executions. By reducing liquidation costs to a fraction of legacy protocols and enabling MEV recapture, Curvance can scale lending safely while maintaining high utilization and faster position response under stress.

#### Native Looping and Scaling

One click leverage gives users immediate exposure to farming strategies without complex manual actions.

#### Adaptive Price Guards for Pegged Assets

Stablecoins, stablecoin vault tokens and LSTs sit inside configurable price bands with heartbeat checks. Breaches below floor halt the market; breaches above ceiling clamp. Both growth parameters and governance updates are tamper‑resistant by construction.

#### Developer Plugins

Modular plugins allow developers to build automated actions or strategy layers directly on Curvance without permissions.

### Vision

Curvance was created to empower users to unlock the full potential of their digital assets. Our founding team believes in a future where financial tools are accessible, secure, and efficient for everyone. Our mission is to make DeFi less intimidating and give users the confidence that they have the best opportunities at their fingertips.

Website: <https://curvance.com/>

X: <https://x.com/Curvance>

Telegram: <https://t.me/curvance>

Discord: <https://discord.com/invite/curvance>


# Protocol Architecture

## **Capital Efficiency and Composability in Curvance**&#x20;

Curvance is purpose-built to bring high-efficiency lending to DeFi. The system is optimized to offer deep liquidity, high LTVs, and dynamic risk processing for assets most relevant to the ecosystem.

### Risk-Isolated Market Design

Curvance structures assets into isolated markets based on risk profile and liquidity depth. Each market operates with its own collateral caps and configuration parameters, allowing us to safely support leverage across approved collateral types without exposing the broader system to concentrated risk.

Assets and future yield-bearing integrations can be deployed into markets that match their risk curve. Lower-risk collateral can support higher LTVs and deeper liquidity access, while newer or lower-liquidity tokens can be onboarded through smaller, more conservative markets. This structure avoids the typical liquidity bottlenecks seen in shared-pool systems and enables capital to scale efficiently once demand is proven.

### Composability

Curvance is built to make capital productive across multiple layers of DeFi without forcing users into fragmented execution flows. Supported deposits can be routed into yield sources or integrated strategy layers while remaining available as collateral. This lets users borrow, farm, or loop positions without losing underlying returns.

The architecture also supports advanced strategy execution beyond basic supply and borrow.

It enables:

* **Funding rate arbitrage**, where users can take directional or neutral positions (e.g. long spot, short futures) while using the Curvance lending layer to deploy capital efficiently.
* **Strategy stacking,** where yield from one layer (e.g., LST staking) can be compounded into additional layers, such as lending or leveraged looping.
* **Cross-market optimization**, where assets can be rotated between markets depending on desired risk settings or collateral demands, without needing complete manual unwinds.

For builders, Curvance acts as a liquidity substrate that can be integrated directly into execution workflows or hosted vault structures. For users, it functions as a hub where assets remain active regardless of whether the goal is yield, leverage, risk hedging, or directional exposure.


# Asset Types

What types of assets does Curvance support?

### Yield-Bearing Assets

The surge in popularity of yield-bearing assets spans wide, from assets such as LSTs, LRTs, yield-bearing stablecoins, Perpetual Exchange/DEX LP tokens, and more. This growing asset class offers both fungibility and income-generating potential.

With Curvance, users can embrace a new paradigm that removes the compromise between participation in lending protocols and yield farming across DeFi. This enables individuals to explore new strategies, such as providing liquidity on Aerodrome while simultaneously borrowing against their LP tokens, maximizing both composability and capital efficiency.

The flow on Curvance will be as follows:

1. A user is interested in unlocking capital on their yield bearing asset. The Curvance protocol will facilitate the user's redirection of the supplied assets back into the respective underlying protocol to earn the native yield while permitting the user to borrow against their position as it remains productive.
2. Upon deposit, the user will receive representative pTokens (position tokens) minted from Curvance, equal to their pool share. These pTokens will redeem their share of deposits after the loan is paid back.

This unlocks the potential for users to leverage Curvance's advanced position looping, enabling participation in traditional strategies like LRT or yield-bearing stablecoin looping and more advanced strategies such as LP token looping.

Through advanced position looping, users can borrow against their LP tokens while continuing to earn the underlying yield. The borrowed funds are then zapped into the underlying protocol's LP token and redeposited into Curvance, effectively creating a leveraged yield farming position.

### **Non-Yield-Bearing Assets**

The Curvance protocol accommodates a wide range of assets, including non-yield-bearing tokens like WETH, WBTC, USDC, and USDT. These assets, though lacking native yield generation, still play a vital role in DeFi by providing liquidity and stability within lending markets.

With Curvance, users can unlock the capital potential of non-yield-bearing assets without sacrificing flexibility. Unlike traditional lending protocols, which limit non-yield-bearing assets to passive roles, Curvance integrates them into a composable framework that allows users to borrow, lend, and manage positions across multiple chains.

How It Works on Curvance:&#x20;

1. A user can deposit non-yield-bearing assets such as WBTC into a market on Curvance, using the asset as collateral for leveraging their original exposure or accessing liquidity without losing their original exposure. While these assets do not generate native yield, users benefit from access to CVE emissions and protocol incentives, enhancing passive yield on their holdings.
2. Upon deposit, users receive pTokens, representing their share in the pool. These pTokens can be redeemed for the original collateral once any outstanding loan obligations are repaid.


# Liquidity Markets

Curvance enables users to deposit one asset as collateral while the protocol lends that liquidity to another user who borrows a different asset. Both sides of the market can remain productive, allowing deposits to generate yield while also unlocking borrowing capacity.

### Supply Side

When you deposit an asset into Curvance, it enters the lending pool and becomes available for other users to borrow. Your deposit will typically continue earning underlying yield if the asset supports it. In most markets, deposits can also be enabled as collateral to unlock borrowing power.

**Key Advantages**

* Earn interest from borrowers and, where supported, from underlying yield sources.
* Optionally enable deposits as collateral to access liquidity.
* Withdraw at any time after the 20 minute cooldown unless the market is fully utilized.

{% hint style="info" %}
**Why is there a 20-minute wait after I add collateral or borrow?**

Whenever you deposit collateral or take out a borrow, Curvance applies a 20-minute holding period during which you can't withdraw, repay, or transfer that position. This protects against price-manipulation attacks, where someone briefly distorts an asset's price to open an oversized position and cash out before the price corrects. Once 20 minutes have passed since your last deposit or borrow, your position behaves normally again.
{% endhint %}

### Borrow Side

**Access liquidity while keeping assets productive**

Users can borrow asset B against their deposit of asset A. Deposited collateral continues to earn yield while enabling access to new capital. This opens the door to strategies such as looping, hedging, funding rate arbitrage, or directional exposure.

**Key Advantages**

* Access industry leading LTVs&#x20;
* Borrow while retaining yield on collateral.
* Access liquidity without selling or unwinding your existing position.
* One click leverage and strategy execution through position manager tools.

{% hint style="info" %}
Some markets are **unidirectional**, meaning only one side of the pool can be borrowed, while the other side may only be supplied as collateral and cannot be borrowed. This structure is typically used for pools with yield-bearing collateral assets, such as LSTs. Other markets are **bidirectional**, meaning both sides of the pool can be supplied as collateral and borrowed, and are typically used when both assets are standard ERC-20 tokens.
{% endhint %}


# Borrowing

Credit lines for depositors inside Curvance

## **Borrowing Limits**

A user’s borrowing capacity in Curvance is determined by two key factors: the collateralization ratio and the liquidity available in the isolated market.

**1. Collateralization Ratio**

The collateralization ratio defines the maximum borrowing threshold for each asset, reflecting its specific risk profile. Assets with lower risk have higher collateralization ratios. For example, an asset with a 75% collateralization ratio allows a user to borrow up to $0.75 for every $1.00 of the asset deposited as collateral.

Curvance calculates each user’s borrowing limit as a blended collateralization ratio across various assets within an isolated market. This blended Loan-to-Value (LTV) ratio represents the maximum amount users can borrow based on the combined collateral they’ve supplied.

*Example:*\
*A user deposits $100 of supported ETH-based collateral into an isolated market. If that asset has a 75% collateralization ratio, the user can borrow up to $75 against it, assuming enough liquidity is available in the market.*

**2. Available Liquidity**

A user’s ability to borrow also depends on the pool’s liquidity. If the requested loan amount exceeds the available liquidity in a given pool, borrowing may not be possible.&#x20;

*Example:*\
*A user deposits $1000 of cbBTC into a market with $50 in available USDC liquidity on the lending side. With a collateralization ratio of 90%, the user can borrow up to $900 in assets against their wrapped bitcoin. Still, with only $50 in available liquidity, the user can only borrow up to 50 USDC even though the Protocol Risk Engine would support a larger debt position.*

***

## **Repaying Loans and Collateral Redemption**

To close a debt position, users must repay the borrowed amount and any accrued interest costs in the same asset initially borrowed. This can be done via the Curvance front end or directly through smart contracts. After repayment, users can redeem their collateral and underlying assets by returning the cTokens received at the time of the initial deposit.

{% hint style="warning" %}
Redeeming collateral is subject to the 20 minute cooldown.
{% endhint %}

***

## Minimum Loan Size

* Each market enforces a minimum active loan size (USD, typically $10–$100).
* New borrows/increases must keep total debt ≥ the minimum; otherwise blocked.
* Partial repayments are allowed, but you can’t leave debt below the minimum. Repay to zero instead.
* Liquidations may reduce debt below the minimum; afterward, borrowers must either fully repay or bring the position back ≥ the minimum before any further partial actions.

**Purpose:** prevent dust loans and ensure liquidations remain economically viable.


# Liquidations

## **Liquidations in Curvance**

Liquidations play a vital role in maintaining the stability and integrity of the Curvance protocol by protecting lenders from potential losses. Liquidations are triggered when Position Health falls below 1 (i.e., collateral capacity at the Margin Requirement is insufficient to cover debt).

### **Understanding Position Health**

Position Health measures how much your collateral capacity at the Margin Requirement covers your current debt.

{% hint style="info" %}
*Position Health = Collateral Capacity at Margin Requirement /* Debt

*Where:*

*Margin Requirement (Soft Collateral Requirement): The minimal collateral buffer required to avoid soft liquidation (e.g., 140%). Curvance applies an auction buffer when evaluating this capacity.*

*Collateral Capacity at Margin Requirement: Your collateral value, weighted by Margin Requirement and Curvance's auction buffer.*

*Debt: Your outstanding debt value in the market.*
{% endhint %}

* If Debt = 0: Position Health is effectively infinite.
* Position Health ≥ 1: sufficiently collateralized (safe under the Margin Requirement).
* Position Health < 1: below the Margin Requirement (eligible for soft liquidation). Hard liquidation uses the hard collateral requirement.

The liquidation engine is proactive and designed to protect borrowers from hard liquidations by implementing a linear scale between "soft" and "hard" liquidation levels. \
\
The severity of liquidations is continuous as collateral runs out and travels along a linear curve from soft liquidation to hard liquidation. The severity of a liquidation is calculated from a user's LFactor or liquidation factor. A liquidation factor of 0 indicates that no liquidation is possible, whereas a liquidation factor of 1 indicates a full hard liquidation.

* **Soft Liquidation:** A partial liquidation occurs with a small penalty, preserving more of the user's collateral, making Curvance more forgiving during times of low volatility.
* **Hard Liquidation**: Full liquidation with a high penalty if the Health Factor is critically low, meaning Curvance can shed risk faster than other lending protocols in times of high volatility.

*Recommendation: Maintaining a Health Factor above 1, ideally at 1.5 or higher during market volatility, is advised to reduce liquidation risk.*

{% hint style="success" icon="eye" %}
Curvance's frontend app expresses position health as a percentage.&#x20;

Example: A risky position has a health of 5%, while lend-only positions has infinite health.
{% endhint %}

{% hint style="info" %}
For more information on LFactor, check out this page: [Dynamic Liquidation Engine](/app/developer-docs/dynamic-liquidation-engine-dle#lfactor-liquidation-factor).
{% endhint %}

***

## **Liquidations in Other Protocols**

Most lending protocols set single-point liquidation levels, creating a trade-off:

* **Overly Conservative Liquidation Levels**: This can lead to premature liquidations, making the user experience less favorable.
* **Overly Generous Liquidation Levels**: This may increase the risk of bad debt within the protocol.

Other lending protocols also tend to only look at three different factors to determine liquidations:

* **Collateralization Ratio:** Determines the maximum borrowing threshold for each asset.
* **Liquidation Threshold:** Equivalent to the Curvance protocol's Hard Liquidation Threshold, this looks at when a position should be liquidated by half or in full.
* **Liquidation Fee:** A fee on the user's collateral value during a liquidation that goes back to the protocol in the form of revenue.

## **Curvance’s Dynamic Liquidation Engine**

The Dynamic Liquidation Engine allows for more flexibility in determining liquidation thresholds, how much of a position gets liquidated, the fee associated with that liquidation, and the incentives for liquidators in each scenario. This is done using the following configurable values:

* **Collateralization Ratio:** Determines the maximum borrowing amount per $1 of collateral for each asset.
* **Soft Collateral Requirement:** The premium of excess collateral required to avoid triggering a soft liquidation.&#x20;
* **Hard Collateral Requirement:** The premium of excess collateral required to avoid triggering a hard liquidation.&#x20;
* **Soft Liquidation Incentive:** The base incentive to liquidate a user position.
* **Hard Liquidation Incentive:** The maximum incentive to liquidate a user position.
* **Liquidation Fee:** The fee the protocol takes from a user's collateral during a liquidation.
* **Base Close Factor:** The % of outstanding user debt that can be closed for a user position.

{% hint style="info" %}
Liquidation Scenario:\
Tony has $1,000 of ETH posted as collateral with $900 in outstanding debt. \
Soft Collateral Requirement = 120%

Hard Collateral Requirement = 110%

Soft Liquidation Incentive = 4%

Hard Liquidation Incentive = 6%

Liquidation Fee = 0%

Base Close Factor = 20%\
\
Tony is below their collateral requirement to avoid soft liquidation (1000 / 120% = $833.33 < $900 but avoids a full hard liquidation (1000 / 110% = $909 !< $900)

Tony has a current LFactor = (900 - (1000 / 120%)) / ((1000 / 110%) - (1000 - 120%)) = 88%\
This results in a liquidation amount of 20% + (100% - 20%) \* 88% = 90.4%,

with a liquidation penalty of 4% + (6% - 4%) \* 88% = 5.76%\
\
Any address could then liquidate Tony by repaying $900 \* 90.4% = $813.6 of their outstanding debt and receive $813.6 \* 105.76% = $860.46 in ETH from Tony.
{% endhint %}

This approach balances the user experience and protocol stability, minimizing the risk of sudden liquidations for borrowers while protecting the protocol and lenders against bad debt.


# Interest Rates

Compensating lenders for their liquidity inside Curvance

## **Dynamic Interest Rates on Curvance**

Interest rates within the platform are dynamic, adjusting in real-time to meet market demand within each lending pool. Inspired by models like Fraxlend, Rari, and Kashi, Curvance’s rates are determined by factors including pool utilization, usage multiplier, and time decay, ensuring stability and flexibility as conditions change.

### **Interest Rate Adjustments**

* **Pool Utilization**: This metric reflects the proportion of total funds in a lending pool actively borrowed by users. A higher pool utilization rate indicates that a larger portion of the pool’s funds are being borrowed, while a lower rate shows more lending capacity.
* **Interest Rate Dynamics**: As pool utilization increases and nears maximum capacity, interest rates progressively rise, significantly beyond a set “vertex point.” This vertex point serves as a threshold where rates begin to accelerate, responding dynamically to increased demand and incentivizing liquidity supply.
* **Time Decay**: Interest rates adjust with a gradual decay over regular intervals (example: every 10 minutes). If utilization drops below the target rate, the vertex multiplier lowers, leading to a decrease in rates to maintain stability.

### **Example Scenario:**&#x20;

*Initial State*: A lending pool contains 1,000,000 USDC, of which 700,000 USDC is borrowed, resulting in an 70% utilization rate with an interest rate of 2%.

* **Surge in Utilization**: If a large borrower enters and takes an additional 200,000 USDC, pushing utilization to 90%, the interest rate rises significantly due to exceeding the 85% vertex point. In this scenario, rates increase to 8%, which then increase every 10 minutes until utilization decreases.

This dynamic adjustment encourages borrowers to consider repayment while incentivizing suppliers to add more USDC to the pool, helping balance demand and maintain liquidity. The system’s responsiveness promotes an equilibrium within the lending ecosystem, supporting stability and addressing the evolving needs of the protocol users.


# Oracles

Pricing assets inside Curvance

Curvance’s oracle system is designed to protect the platform and users from unsafe price feeds. It checks whether prices are fresh, applies guardrails to assets that should move within predictable ranges, and uses oracle values to sanity-check protocol swaps. Together, these protections help reduce the chance that stale data, sudden price spikes, or poor swap execution can create unsafe borrowing, redemption, liquidation, or zap outcomes.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2Fah8tr5WsZfX1mGU7ngM2%2Fimage.png?alt=media&amp;token=6573caef-f2d1-4bcb-92cf-6bfbf6090725" alt=""><figcaption></figcaption></figure>

#### Freshness Validation

Curvance enforces per‑asset heartbeat checks to ensure timely price updates. Stale or missing feeds are ignored; prolonged staleness can trigger asset‑level circuit breakers.

* **Heartbeat checks:** If a feed misses its heartbeat window, it’s excluded from pricing until fresh data arrives.
* **Targeted pauses:** Sustained staleness or divergence pauses new borrows/redemptions for that asset.

***Example:***&#x20;

*If one ETH price source stops updating, Curvance uses the other and puts the asset in a cautious state: new borrowing and redemptions are paused, while liquidations still proceed to protect lenders. If the two sources later differ by a lot, the asset is fully frozen (including liquidations), until prices align; smaller differences keep it in the cautious state.*

#### Adaptive Price Guards

Price guards apply conservative min/max bands to filter outliers. Bands can be static for stable assets or dynamic—gradually increasing the ceiling—to follow sustained uptrends while blocking sudden spikes.

* **Static vs. dynamic:** Static caps for tightly pegged assets; dynamic bands lift the maximum price over time (small increase‑per‑second) to reflect real yield accrual.

***Example:***&#x20;

*sAUSD accrues yield above $1.00. The guard sets a floor at, say, $0.995 and a dynamic ceiling that starts near $1.00 and inches up daily.*

* *If a feed prints $0.98, it’s rejected (below floor).*
* *If a feed spikes to $1.05 instantly, it’s capped to the current ceiling (e.g., $1.01).*
* *In steady markets, the ceiling rises gradually with yield, allowing sAUSD to price above $1 without triggering false liquidations.*

### Safer Swaps

Curvance also uses oracle pricing to protect protocol-managed swaps. Some user actions, such as zaps or position-management flows, may route through external swap aggregators. Before Curvance accepts the result, the swap is checked against both the swap instructions and the oracle value of the tokens involved.

**Calldata checks:** Curvance verifies that the swap call matches the expected input token, input amount, output token, target, and recipient before execution.

**Value checks:** After the swap executes, Curvance compares the oracle value of what went in with the oracle value of what came out.

**Slippage enforcement:** If the received output is worth less than the allowed slippage threshold, the transaction reverts. If Curvance cannot safely price either token, the swap also reverts.

***Example:***&#x20;

*A user routes a zap that swaps $1,000 of USDC into WETH. The external router may include its own minimum-output check, but Curvance adds another layer by comparing the oracle value of the USDC spent against the oracle value of the WETH received. If the swap only returns $940 of WETH value and the allowed slippage is 3%, Curvance rejects the swap instead of accepting the poor execution.*

### Preferred Oracle Partners

When available Curvance leverages Chainlink feeds to price listed assets. This is due to the highly secure technical assumptions and MEV architecture Chainlink Oracles offer. In cases where Chainlink oracles are unavailable other oracle solutions may be temporarily utilized.


# Collateral and Debt Caps

Minimizing systemic risk

### **Collateral and Debt Caps in Curvance**

Collateral and debt caps are both core safeguard for the lending markets, setting limits on how much of an asset may be used as collateral and how much may be borrowed. These caps help protect the protocol against bad debt and reduce incentives for manipulation by constraining market-wide exposure on both the collateral and borrow sides.

### **Purpose and Function of Collateral Caps**

While the protocol allows unlimited vault deposits to earn yield, only a percentage of total assets in each market can be used as collateral for borrowing. By setting these collateral caps, the Curvance protocol minimizes the risks posed by market volatility and sudden price shifts, aligning with industry risk management principles to ensure the platform’s stability.

* **Mitigating Overexposure**: Collateral caps prevent overexposure to specific assets within isolated markets, reducing potential adverse impacts during volatile market conditions.
* **Ensuring Controlled Borrowing**: Collateral caps create an over-collateralized borrowing environment, mitigating systemic risk while providing users with a secure lending and borrowing experience.

### **Determining Collateral Caps**

Collateral caps are set by the Curvance team in coordination with the asset issuer or token partner. When setting a cap, the team reviews the asset’s available liquidity, market depth, trading venues, and expected use within Curvance.

The goal is to support healthy market growth while keeping each cap aligned with the liquidity that could realistically support liquidations and risk management. Caps may be adjusted over time as liquidity conditions, usage, and partner coordination evolve.

***Example**:*\
*If USDY constitutes 80% of a skewed stable pool, with USDC making up 20%, the collateral cap for USDY is calculated based on USDC’s liquidity. If total offside liquidity is $10 million (with $8 million in USDY and $2 million in USDC) and Curvance allows a cap of 40% of offside liquidity, the collateral cap would be 40% of $2 million, or $800,000, translated into tokens based on asset value.*

### **Collateral Cap Example: On-Chain Liquidity Focus**

Consider $100 million in sDAI within the Curvance protocol. With a focus on on-chain liquidity, the protocol caps collateralization for sDAI at approximately 10 million tokens. This cap means that no more than 10 million sDAI can be used as collateral, ensuring controlled asset exposure while minimizing systemic risk.

### Purpose and Function of Debt Caps

Debt caps limit the total amount of an asset that can be borrowed in a given market. They reduce systemic risk from liquidity crunches, oracle disruptions, and recursive leverage loops by bounding aggregate borrow exposure.

When a market’s total borrowed amount reaches its debt cap, new borrows revert until utilization falls below the cap. Repayments and liquidations remain fully allowed.

Caps are sized to ensure that, under stressed conditions, outstanding debt can be reasonably unwound into on-chain liquidity without causing outsized slippage or prolonged insolvency risk.

### Determining Debt Caps

Debt caps are set by the Curvance team in coordination with the asset issuer or token partner. When setting a debt cap, the team reviews borrow demand, available liquidity, asset volatility, market depth, and how easily positions could be unwound during stressed conditions.

The goal is to let markets grow while keeping borrowing limits aligned with realistic liquidity and liquidation capacity. Debt caps may vary across isolated markets for the same asset and can be updated over time as market conditions, usage, and partner coordination evolve.

### Debt Cap Example

Behavior at Cap

* **New borrows:** Revert once the market reaches its debt cap.
* **Repayments and liquidations:** Always allowed.
* I**nterest:** Continues to accrue as normal; utilization near the cap will still interact with interest rate parameters.

**Example:**

*Suppose WETH has approximately $200M in reliably accessible on-chain liquidity across primary routes. If the DAO targets a 20% maximum borrow exposure for stress-unwind assumptions, the initial debt cap would be $40M equivalent (translated into tokens based on price). New borrows beyond $40M revert until outstanding debt decline.*

***

### Conclusion

By linking collateral and debt caps to market liquidity, asset behavior, and partner coordination, Curvance creates a more controlled approach to collateralized borrowing. This dual-cap system helps protect users and the broader protocol by managing exposure on both sides of the market while still giving users room to scale responsibly.


# Bad Debt Socialization

## **Bad Debt Socialization in Curvance**

Bad debt socialization is a critical mechanism within the Curvance protocol, designed to maintain market stability and manage risk in cases where borrowers default on their loans, leaving a shortfall in collateral. For example, if a borrower owes $500 but has collateral worth only $300, a $200 shortfall arises.

Bad debt socialization addresses isolated and cross-margin scenarios, providing a nuanced solution that differentiates it from other protocols.

### **How Bad Debt Socialization Works**

When an undercollateralized position is liquidated, and a shortfall remains (e.g., $200), the deficit is socialized across the entire lender market to protect market health. This process involves an **adjustment to the exchange rate** of each lender’s token, allowing the shortfall to be absorbed proportionally by all lenders:

* **Proportional Distribution**: The shortfall is distributed across all lenders within the affected market. Each lender’s token value for redemption is slightly reduced to cover the debt, ensuring that the impact on each lender is proportional to their market participation.
* **Exchange Rate Adjustment**: Adjusting the exchange rate systematically distributes the deficit, preventing any single lender from bearing an excessive share of the loss. This method stabilizes the market and keeps it operational, even in significant default events.

### **Rationale and Lender Risk**

Bad debt socialization aligns with the inherent risks lenders assume when participating in the protocol. Since lenders are exposed to borrower defaults, sharing the impact of bad debt across all participants is an equitable solution. This approach decreases risk to lenders and strengthens the protocol’s resilience by preventing bad debt accumulation that could destabilize the market.


# Application Specific Sequencing

## **Application Specific Sequencing in Curvance**

The Curvance protocol introduces a MEV-optimized system for handling liquidations, built to prioritize Chainlink-supported oracle infrastructure where available. The system integrates application-specific sequencing with orderflow auctions, allowing liquidators to compete for the right to liquidate collateral in Curvance lending markets.&#x20;

This gives Curvance a way to capture liquidation value that would otherwise flow to external MEV participants, while keeping liquidations fast, competitive, and aligned with protocol health.

### **Liquidation Characteristics**

Through app-specific sequencing, the protocol captures Maximum Extractable Value (MEV) by organizing liquidation events to maximize efficiency and value for the protocol. Here’s a simple breakdown of how it works:

1. **Orderflow Auctions**: When a liquidation event is triggered, liquidators participate in an auction, bidding for the right to execute the liquidation. This competitive bidding process allows the protocol to receive the transaction validation bid rather than the block builder.
2. **Rapid Execution**: The entire auction process takes just 300 milliseconds (3/10ths of a second), ensuring liquidations occur quickly and with minimal delay.
3. **Fail-Safe Permissionless Liquidation**: If no winning bid is determined or the winning liquidator fails to execute, a permissionless liquidation immediately takes place to protect the protocol’s stability and assets.

### Incentive Capture

Over the last four years, traditional platforms like Compound and Aave have left a combined $180 million in liquidation incentives to MEV searchers. Historically, 95 - 98% of all incentives are given as incentives to block builders to validate their liquidation first. Curvance’s MEV-optimized system is designed to recover as many incentives as possible through auction revenue, significantly reducing the opportunity cost of liquidations while increasing protocol revenue potential.

### Competitive Edge

With the built-in fallback mechanism, the protocol remains competitive with other DeFi platforms (Lending Protocols, Perpetual Exchanges, Collateralized Debt Positions Protocols, etc.), which may need to liquidate user assets. Curvance can capture MEV without needing an appchain, minimizing costs, enhancing protocol sustainability, and providing a unique advantage for users and liquidators.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FvZRbI1AnIoHDel6MbUBm%2Fimage.png?alt=media&amp;token=a806184b-5c80-4407-bd0a-bb36a4a34745" alt="" width="563"><figcaption></figcaption></figure>


# Earn Vaults

Curvance Earn Vaults turn fragmented lending markets into one clean Earn position. Because that position is tokenized as vault shares, it can also be used in Curvance strategies like looping.

Users deposit one supported asset, receive vault shares, and let Curvance route the underlying liquidity across approved lending markets. The user experience stays simple. The vault does the work.

**The short version:**

* one asset goes in;
* one Earn position comes out;
* Curvance can move liquidity as market conditions change.
* defensive checks help route away from unhealthy markets.

**That is the unlock:** users get a simple Earn balance, while Curvance gets a smarter way to keep lending liquidity productive.

### Why It Matters

Lending markets move constantly. Borrow demand changes, liquidity enters and leaves, rates shift, and some markets become more attractive than others.

Without Earn Vaults, users have to pick a market and keep watching it. Earn Vaults compress that loop into one Curvance-native position, so Curvance can rebalance toward stronger opportunities while still respecting caps, liquidity, pauses, and safety checks.

It is yield routing for people who would rather be earning than tab-hopping.

### How It Works

Each Earn Vault is built for one underlying asset, such as USDC. Behind that one asset, the vault can hold positions in several approved Curvance lending markets that use the same underlying.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FRJgceYUIF1j4LsJuhp33%2Fimage.png?alt=media&amp;token=f1981020-1136-44a6-86ba-c10c7c5a5a2c" alt="" width="375"><figcaption></figcaption></figure>

Users hold shares of the Earn Vault. Those shares represent their slice of the total assets deployed across the vault's approved markets.

Deposits add assets across the vault's current market mix. Withdrawals pull assets back from that mix when liquidity is available. Rebalances are what change the vault's target liquidity mix.

### Rebalancing

Earn Vaults are paired with `OptimizerReader`, which acts like the route planner.

The reader looks across an Earn Vault's approved markets and asks:

* where is the vault's liquidity right now?
* which markets have idle liquidity?
* which markets have stronger supply rates?
* how much cash, debt, and fees sit behind each market?
* which markets are capped, paused, stale, or unsafe to add to?

Then it builds a rebalance plan: withdraw from markets that should shrink, deposit into markets that should grow, and keep each market inside its configured allocation limits.

The plan also includes tolerance bounds. If the market state changes too far before the transaction lands, the on-chain rebalance can reject the plan instead of executing stale instructions. Small moves can be skipped too, so the system does not churn dust just to look busy.

### Defensive Routing

This is where Earn Vaults get especially powerful.

The same rebalance system that searches for better yield can also protect the vault from markets that no longer look healthy. `OptimizerReader` checks the collateral side of each approved lending market and can flag a market as bad when either of these conditions appears:

* the collateral oracle feed is stale;
* the collateral price has breached a configured price guard.

The staleness check is a freshness filter. If the oracle has not updated within its allowed heartbeat window, the reader treats that market as unreliable.

The price guard is a floor check for bounded collateral assets. If the reported collateral price falls through the configured guard, the reader can treat that market as unsafe for new allocation.

When a market is flagged, the rebalance planner excludes it from the ideal allocation. If the market can be exited, the plan can pull liquidity away and redistribute it into the remaining good markets. If the market is redeem-paused, the vault keeps that position frozen instead of pretending stuck liquidity is movable.

So in normal conditions, Earn Vaults route for yield. In defensive conditions, they can route away from bad collateral signals. That turns rebalancing into more than APY chasing. It becomes active risk routing.

***Example:***

*Alice deposits 10,000 USDC into a USDC Earn Vault. She does not need to pick between three different USDC lending markets. She receives Earn Vault shares, and Curvance manages the routing behind the scenes.*

*Now say one USDC market gets flooded with new liquidity, pushing its supply rate down, while another approved market has stronger borrower demand. `OptimizerReader` can compare the current mix against the better available mix and produce a plan to move liquidity toward the stronger market. Users still hold the same Earn Vault shares.*

*Or suppose a collateral oracle stops updating, or a bounded collateral asset breaks through its price guard. The reader can mark the related lending market as bad. The next defensive rebalance can stop treating that market as a target and, when exit conditions allow, move vault liquidity toward healthier markets.*

*That is a stronger story than "we chase APY." Earn Vaults can chase yield and watch the road at the same time.*

### Not Blindly Chasing APY

Higher APY is not the only input. Each Earn Vault also respects allocation caps.

If one market briefly shows the best rate but has a 20% cap, the vault cannot throw everything into it. The post-rebalance allocation must stay at or below the cap.

Good routing is not just finding the biggest number. It is finding better yield inside a controlled risk box.

### Withdrawing

Bob wants 1,000 USDC back from his Earn Vault position. He does not need to know which markets currently hold the USDC. He redeems from the vault, and the vault pulls liquidity back from approved markets according to the current mix and available liquidity.

If a market is paused for redemptions, the Vault prevents early users from withdrawing from only the healthy markets while later users are left bearing the paused one. This is a fairness choice, not just a technical detail.

### Fees

An Earn Vault can charge a performance fee on new yield. Fees are measured against a high watermark, so Curvance charges performance fees only when the vault reaches new profit highs. Fees are displayed on each Vault’s page in the app.

### What It Does Not Promise

Earn Vaults are powerful, but they are not insurance.

APYs can change. Markets can be paused. Liquidity can become limited. A rebalance only executes when authorized Curvance operations trigger it, and only if the on-chain safety checks pass.

The point is not to promise the highest possible rate every second. The point is to give Curvance a better system for routing lending liquidity over time while keeping the user experience clean.


# Multichain Architecture

## **Wormhole: Enabling Curvance's Multichain Strategy**

Wormhole is a pivotal technology that supports the Curvance protocol's multichain approach by enabling seamless communication across EVM and non-EVM blockchains. Through Wormhole, the protocol can overcome composability and scalability limitations, allowing the Curvance protocol to operate efficiently on multiple chains.

At the core of Wormhole’s architecture is the **Guardian Network**, a decentralized validator network that verifies and signs cross-chain data transfers, ensuring the integrity and authenticity of each transaction. Wormhole’s **Cross-chain querying** and **Standard Relayers** streamline the process by automating data reads and transfers between chains, making it simpler and more cost-effective for developers to build multichain applications.

The Curvance protocol utilizes Wormhole’s technology to power the Multichain Fee System and Multichain Vote Escrow system, bringing greater scalability and interoperability to the Curvance ecosystem.

For an in-depth explanation of Wormhole’s technology, visit the [Wormhole Docs](https://wormhole.com/docs/).

## **Circle: Powering Curvance's Multichain Fee Distribution**

The Cross-chain transfer protocol (CCTP) facilitates Curvance's multichain fee distribution system, by enabling zero slippage value transfer between EVM and non-EVM chains.&#x20;

### How it works <a href="#how-it-works" id="how-it-works"></a>

1. **USDC is burned on the source chain**: Using an app, a user initiates a transfer of USDC from one blockchain to another, and specifies the recipient wallet address on the destination chain. The app facilitates a burn of the specified amount of USDC on the source chain.
2. **A signed attestation is fetched from Circle**: Circle observes and attests to the burn event on the source chain. The app requests the attestation from Circle, which provides authorization to mint the specified amount of USDC on the destination chain.
3. **USDC is minted on the destination chain**: The app uses the attestation to trigger the minting of USDC. The specified amount of USDC is minted on the destination chain and sent to the recipient wallet address.

CCTP's value proposition is increased when combined with the aforementioned Wormhole tech stack. Allowing Curvance to facilitate value transfer and information transfer simultaneously between chains. Curvance utilizes CCTP to power our Multichain fee system, bringing greater scalability and interoperability to the Curvance ecosystem.

For an in-depth explanation of CCTP, visit the [Circle Docs](https://developers.circle.com/stablecoins/cctp-getting-started).


# Multichain Gauge System

## **Multichain Gauge: Enhancing Voting and Rewards**

The Curvance Multichain Gauge is a system designed to build on the traditional vote-escrow model by enabling cross-chain voting and reward distribution, a feature unavailable in most gauges that limit governance participation to a single chain.

### **Key Features of the Multichain Gauge**

* **Cross-Chain Gauge Emission Voting**: Users can lock CVE across all supported chains, enabling them to vote for native gauge emissions regardless of their lock location. This system allows for flexible and robust liquidity strategies.
* **Reward Distribution Across Chains**: CVE token lockers can receive pro-rata protocol fees generated across all integrated chains. As a result, this allows veCVE holders to benefit from fees generated across the entire Curvance ecosystem, regardless of the blockchain their lock is located.
* **Cross-Chain Lock Bridging:** Users can burn and mint their veCVE locks to move them from chain to chain, where pro-rata chain rewards are available, driving volume and TVL to a particular chain.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FTTjTrY5C8DpBPLFLFNkn%2Fimage.png?alt=media&amp;token=a64bb7f8-4d48-4b04-b6e3-3d99bcdca374" alt=""><figcaption></figcaption></figure>

**How the Multichain Gauge Works**

Using **Snapshot’s gasless voting system**, voting occurs during biweekly epochs. Once an epoch concludes, votes are aggregated and processed on onchain.&#x20;

> [Snapshot](https://snapshot.org/) is a decentralized platform that facilitates governance in decentralized applications and protocols. Its gasless system allows users to participate in voting and decision-making processes without needing to pay gas fees, making participation more accessible and user-friendly.&#x20;

{% hint style="info" %}
Votes are frozen 12 hours prior to and 12 hours after an epoch by locking all state changes to veCVE locks, preventing vote double-spending. Read more in [Multichain Vote Escrow](/app/protocol-overview/multichain-architecture/multichain-vote-escrow-system).
{% endhint %}

Emissions remaining for an epoch are queried across all chains simultaneously via Wormhole's CCQ, validating the efficacy of the aggregated voting data. Once validated, emission data is updated on the source chain, and remote chain emission data is relayed via Wormhole's standard relayer to each chain. Gauge Rewards are made available in the **Gauge Manager** on each chain.

## **Benefits of the Multichain Gauge**

Curvance’s multichain gauge introduces a unified, chain-agnostic vote-escrow system that reduces gas costs, promotes deep liquidity, and enables efficient fee generation across multiple chains with minimal maintenance. This design empowers users to lock CVE on any chain, vote for any pool, and share in protocol fees equally, enhancing governance flexibility and ecosystem growth.

For a visual overview of the cross-chain communication system, please see the diagram below.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FSuIdORP1l4TGCohdu3Se%2Fimage.png?alt=media&amp;token=bce665e7-b112-4e4c-8daf-d2e00534c20e" alt=""><figcaption></figcaption></figure>


# Multichain Vote Escrow System

## **Multichain Vote Escrow System**

The **Multichain Vote Escrow System** is a core component of Curvance DAO governance and incentive design, enabling holders to actively participate in the protocol’s development direction and benefit from multiple revenue streams across chains. By locking CVE tokens for veCVE, users gain governance power that extends across Curvance’s multichain ecosystem.

{% hint style="info" %}
Curvance's veCVE contracts freeze all state-changing operations (creation/compounding on locks, unlocking, combining locks, etc.) 12 hours prior and 12 hours after an epoch. If, for some reason, fees are not distributed to a chain, state change operations will remain halted until rewards are resolved, preventing double spending or loss of rewards.
{% endhint %}

### **Cross-Chain Governance and Incentive Flexibility**

One of the unique features of Curvance’s multichain vote-escrow system is the ability for veCVE holders to participate in governance across chains seamlessly. Through the CVE Gauge System, veCVE holders can direct emissions and influence incentives on any supported chain, allowing for a highly flexible and powerful governance model.

* **Migrating Locks Across Chains**: veCVE holders can migrate their locks to integrated chains, allowing them to access chain-specific incentives. This feature gives users flexibility and enhances the protocol's network reach, attracting liquidity and activity to targeted chains.
* **Multichain Fee Distribution**: veCVE holders receive fees generated across all integrated chains, regardless of where their lock is held. This is a key innovation compared to traditional vote escrow models, which limit users to receiving fees and exercising voting power solely on the chain where their tokens are locked.&#x20;

{% hint style="info" %}
Each chain reports its veCVE-locked balances at the end of each epoch, allowing the Messaging Hub to calculate and distribute protocol rewards (paid in USDC) across all supported chains. Rewards on the source chain become instantly available, while rewards for other chains are bridged through the cross-messaging system. Rewards are made available in the **Reward Manager** on each chain.
{% endhint %}

The Multichain Vote Escrow system is more than just a governance mechanism—it is a comprehensive structure for aligning user incentives, facilitating cross-chain incentives, and reinforcing Curvance’s mission of building a multichain DeFi ecosystem. By empowering veCVE holders with voting power and shared fee distributions across chains, Curvance incentivizes meaningful, long-term engagement and growth throughout its ecosystem.


# New Age Liquidity Mining

## **New Age Liquidity Mining with Curvance**

The Curvance protocol provides new innovations in liquidity mining infrastructure by streamlining incentive programs for protocols, token issuers, and blockchains while simplifying participation for liquidity providers and users. \
\
Traditionally, setting up secondary and tertiary incentivization layers involves complex processes and additional steps for users, leading to a measurable drop in participation and adoption. Curvance’s infrastructure delivers incentives directly while optimizing the underlying liquidity mining strategies for greater efficiency and growth. Additionally, user liquidity can be routed through multiple layers of incentivization, ensuring peak capital efficiency and maximizing yield.


# Protocols

### **Strategic Advantages for Protocols**

The Curvance platform enables other protocols to run incentive campaigns tailored to their specific objectives. By utilizing Curvance, protocols can enhance existing liquidity mining and product growth strategies while introducing new utility and maximizing yield opportunities for their users. This is accomplished by:

* **Optimization of DEX Liquidity Mining:** The Curvance protocol optimizes DEX liquidity mining for other protocols by creating vaults that support their DEX liquidity pool tokens. These vaults natively auto-compound underlying emissions and incentives back into LP tokens. This leads to deeper, continually growing DEX liquidity for protocols and higher yields for liquidity providers on Curvance.
* **Ease of Incentivization:** Protocols can seamlessly stream any whitelisted ERC-20 token through the **Partner Gauge System**. This includes ecosystem grants and the protocol's native token, offering a flexible approach to incentivization.
* **Asset Looping and Leveraging**: Protocols can incentivize users to leverage their positions through one-click asset looping. This allows users to leverage basic yield-bearing assets and long-tail exotic assets, such as Decentralized Exchange LP tokens and Perpetual Exchange LP tokens, unlocking a new market of liquidity providers for protocols to capitalize on.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FvUCcE29un01nZGvbtL7E%2Fimage.png?alt=media&amp;token=ce36568b-1df0-47a5-af69-e95f5e09795d" alt=""><figcaption></figcaption></figure>


# Chains

### **Strategic Advantages for Chains**

Blockchains can utilize the Curvance platform to attract new users and liquidity from competing networks via the **Multichain Gauge System** and **Multichain Vote Escrow System**. Curvance enables this by improving:

* **Opportunity Discovery:** Exclusive opportunities on chains can be highlighted by increasing visibility among Curvance users since all available strategies can be viewed on the same dashboard. This increased exposure helps chains attract interest and engagement from users across other networks, resulting in increased ecosystem participation.
* **Advanced Liquidity Mining Infrastructure**: the multichain architecture of the protocol empowers chains to drive growth to their DeFi ecosystems by attracting users and liquidity from other networks through grants or liquidity incentivization campaigns. By integrating these campaigns through the **Partner Gauge System**, chains can effectively target and incentivize specific areas of growth within their ecosystem.
* **Strategic Ecosystem Growth**: The Curvance platform offers a new approach for blockchains to drive ecosystem growth, going beyond traditional airdrops, incentive campaigns, or grants.  By utilizing the **Multichain Vote Escrow System**, chains can acquire veCVE lock dominance by incentivizing veCVE lock holders to migrate their locks to their chain. This also exposes the chains to the biweekly rewards distributed to lockers on their chain. This strategy enables chains to achieve measurable impacts on total value locked and transaction volume, fostering long-term, sustainable ecosystem growth.&#x20;


# How Are New Assets Integrated

### **How Are New Assets Integrated into Curvance?**

There is a structured process for integrating new assets, ensuring each addition is carefully evaluated, secure, and beneficial to the ecosystem. This multi-step approach involves collaboration with asset teams and Curvance DAO contributors, due diligence on infrastructure, and rigorous testing, leading to seamless integration within the protocol.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FxcUVlcS5mHOfBo5EMqxL%2Fimage.png?alt=media&amp;token=72d88803-ee01-43a1-84ba-44e7802a613b" alt=""><figcaption></figcaption></figure>

**1. Initial Conversation**

The integration process begins with an initial conversation, typically initiated through business development efforts or inbound requests from teams looking to have their assets supported on Curvance. These discussions aim to understand the asset’s potential and explore collaborative opportunities.

**2. Infrastructure and Compatibility Assessment**

Both the assigned Curvance development team and the asset's team verify the availability of critical infrastructure, such as price oracles and other relevant data feeds. This assessment helps determine the type of integration possible, factoring in considerations such as:

* Total available liquidity for the asset
* Whether other lending protocols support the same asset
* The quality and reliability of infrastructure components

**3. Strategy Vault Development**

Based on the information gathered, the teams decide on the optimal type of integration for the asset. This decision balances factors such as liquidity, support within the broader DeFi ecosystem, and the asset's potential for yield generation and capital efficiency within the Curvance platform.

The Curvance DAO's assigned engineering team develops a strategy vault tailored for the new asset. This vault is designed to integrate with the existing protocol infrastructure while maximizing yield and liquidity opportunities specific to the asset.

**4. Security Audits and Validation**

The newly developed strategy undergoes rigorous third-party security audits to ensure protocol stability and user safety. This step is crucial for maintaining trust in the protocol's security standards and protecting the broader DeFi community.

**5. Asset Integration and Deployment**

Once the strategy passes the security audit, the new asset is integrated into the Curvance protocol. From this point, users can access and interact with the asset throughout the platform, leveraging its unique yield opportunities, lending capabilities, and liquidity options.

***

This structured approach ensures that each new asset added to Curvance meets rigorous security, liquidity, and usability standards, creating a seamless user experience and maintaining the protocol’s integrity.


# Specialized Blockchain Integrations

## **How Curvance Benefits Supported Chains**

The protocol's integrations with various chains extend beyond simple technical integration; they foster innovation, expand accessibility, and drive new opportunities within each ecosystem. By designing around what these unique networks offer, the protocol enables each chain to amplify its strengths while offering users a distinct experience aligned with each chain’s vision. Let's look at a few examples below:

### **1. Berachain: Building on the Proof of Liquidity Model**

**About Berachain:**\
Berachain introduces a novel Proof of Liquidity model, encouraging DeFi teams to innovate on liquidity mechanisms that generate stronger, self-sustaining economic flywheels within the network.

**Benefits of Collaboration:**\
Protocol support for Berachain benefits other teams in creating liquidity-driven applications that maximize capital efficiency. Through the Curvance platform, these applications benefit from a more dynamic, composable rewards structure, helping Berachain achieve its goal of supporting innovative DeFi projects that leverage liquidity to generate network growth.

**User Experience on Curvance:**\
Curvance users can access yield-generating opportunities specifically optimized for Berachain’s Proof of Liquidity model, allowing them to participate in new flywheels unique to Berachain’s ecosystem. This gives users an experience that leverages high liquidity and fast discoverability of emerging projects.

### **2. Monad: Enabling High-Throughput, Gas-Efficient Applications**

**About Monad:**

Monad’s parallel EVM design enables ultra-fast, efficient transactions that can seamlessly scale to meet the demands of existing projects. This architecture also unlocks new possibilities for projects previously limited by scalability constraints on other chains, fostering innovation in ways not achievable elsewhere.

**Benefits of Collaboration:**\
Protocol support Monad allows dApps to leverage gas-efficient auto-compounding, high-frequency strategies, and interactive liquidity tools, all powered by Monad’s fast, low-cost transactions. This partnership also enables developers to build high-frequency solutions on top of Curvance using the plugin system, expanding user experiences with innovative, scalable DeFi solutions.

**User Experience on Curvance:**\
Within the Curvance protocol, users can explore custom mini-games, high-frequency plug-ins, and gas-optimized yield strategies that offer a distinctive, engaging experience. This collaboration allows Curvance users to experience DeFi in a more interactive, cost-effective way, with applications that are as fast as they are efficient.

## **Creating Unique Experiences Across Chains**

Curvance’s multi-chain support provides a wide range of experiences tailored to each ecosystem’s unique strengths. By connecting with specialized chains like Berachain, Monad, and M2, Curvance offers users a platform where they can discover and interact with distinct opportunities across the DeFi spectrum—whether it’s liquidity flywheels, fun mini-games, or Move-compatible applications. Each chain benefits from increased exposure, user engagement, and network activity, making Curvance a collaborative space that fosters growth across the DeFi landscape.


# Plugin System

The [Plugin System](https://docs.curvance.com/cve/~/revisions/HehateP0Gj1JJbXebEfk/developer-docs/plugin-and-delegation-system) enables unparalleled interoperability and flexibility within the Curvance protocol, allowing users to authorize specific actions by external addresses on their behalf. This structure enhances the protocol’s composability and enables innovative use cases across DeFi, while maintaining user security and control.

### **How the Plugin System Works**

The Plugin System combines elements of Uniswap V4’s hook system with the familiar ERC20 approval process but with advanced features that extend its functionality. Here’s how it works:

* **Authorization for Specific Actions**: Users can grant permissions to external addresses to perform specific actions on their behalf, such as borrowing, collateralizing, and claiming rewards within the protocol. This system enables flexible interactions without requiring centralized approval.
* **Security-Centric Design**: The plugin system was designed with security in mind. All approvals can be instantly revoked across smart contracts, and users can add an additional “lock” on new approvals for an extra layer of protection—essentially creating a two-factor authentication for approvals.

### **Benefits of the Plugin System**

The Plugin System empowers users in several ways:

1. **Enhanced DeFi Composability**: By enabling external protocol logic to be built directly on Curvance, the plugin system supports diverse use cases, such as cross-chain money markets, DeFi strategy abstraction, and balance sheet management for DAOs and institutions.
2. **Accelerated Innovation**: Builders can leverage the full Curvance Protocol and its network effect to develop new solutions without needing to fork the protocol or compete for dominance. This allows for a unified DeFi ecosystem, with each new plugin amplifying the protocol's utility.
3. **Integrated Monetization**: Plugins can implement their own fee structures, creating a clear path for developers to monetize their innovations. This incentivizes further development and a robust ecosystem of interconnected solutions.
4. **User Control and Security**: Unlike the traditional ERC20 approval system, the plugin system prioritizes user control, allowing them to manage and revoke permissions easily. The added lock feature further enhances security by introducing an optional two-factor approval mechanism.

***

The Plugin System in the Curvance protocol unlocks powerful new use cases. It offers a secure, composable foundation for DeFi innovation, enabling the development of cross-chain applications and sophisticated strategies that benefit users, DAOs, and institutions alike.


# Universal Account Balance

Empower applications, chains, payment systems, and more to monetize idle capital.

### **Unlocking Native Yield for Builders**

Curvance’s Universal Balance offers protocols a seamless way to integrate Curvance's yield into their products. The Universal Balance smart contracts enable protocols to route platform assets—such as USDC, USDT, or ETH—into the supply side of Curvance's low-risk money markets. This allows their native users to benefit from an additional passive yield stream without directly interacting with Curvance.

With a single integration, protocols benefit from:

* Enhanced yield is achieved by routing supported assets into Curvance’s lending markets, unlocking an additional source of yield for the protocol and/or its users.
* Accessible liquidity by allowing assets to remain available for immediate use.
* Universal Balance seamlessly integrates into existing vaults and liquidity pools without changing their structure.
* Simplified UX with one-click integrations that work across multiple chains.

### **End Users: Effortless Yield & Liquidity**

Universal Balance automates passive earning while maintaining complete flexibility. Instead of leaving assets unproductive, funds are routed to yield-generating markets without requiring active management. There are no lockups, and users retain full control over their balances, ensuring capital is always available when needed.

### **Security & Risk Mitigation**

Security is a core focus of Curvance, and Universal Balance is designed to minimize risk exposure while maximizing yield efficiency:

* **Risk-Isolated Markets**: Assets are only routed into audited, risk-adjusted lending pools.
* **Dual-Oracles & Circuit Breakers**: Every asset in Universal Balance is protected by [**redundant pricing oracles**](/app/protocol-overview/liquidity-markets/oracles) to prevent manipulation.
* **Non-Custodial & Permissionless**: Universal Balance never takes custody of user funds, ensuring assets remain accessible at all times.

### **Seamless Integration & Composability**

Universal Balance is plug-and-play and requires minimal setup. With smart contract hooks and plugin compatibility, protocols can optimize how their assets flow through Curvance. Its modular design allows for effortless integration with DeFi protocols, automated strategies, and governance systems make it a scalable solution for builders across the ecosystem.

### Example Integration&#x20;

A Telegram trading bot integrates with Curvance’s Universal Account Balance, allowing users to earn passive yield on idle balances while maintaining full trading flexibility. The development team configures a risk profile that aligns with their strategy, giving users the option to opt in or out of yield generation.

Users experience no change in their normal trading interactions—idle funds are automatically earning yield until needed for execution. When a trade is initiated, the bot instantly pulls liquidity from Universal Balance, ensuring seamless transactions without delays. This integration enhances capital efficiency without disrupting the user experience.


# Token Approval Management&#x20;

## **Token Approvals**

Curvance offers a robust and user-friendly token approval system, ensuring users have full control over their assets while interacting with the platform. This system provides flexibility, security, and transparency, making it easier to manage token interactions and minimize risks.

### **Token Approval Options**

1. **1/1 Approvals**: Users can approve tokens for each individual transaction, providing maximum control and limiting risk.
2. **Infinite Approvals**: For convenience, users can approve tokens once for unlimited transactions with that asset, eliminating the need for repeated confirmations.

### **Approval Revocation**

Curvance includes an **Approval Revoke System** that serves as a public good, which allows users to:

* **View Current Approvals**: Check all active approvals to monitor asset permissions.
* **Revoke Specific Approvals**: Remove approval for individual assets.
* **Revoke All Approvals**: Instantly cancel all token approvals for enhanced security.

This feature ensures users can stay updated on their approval exposure and revoke access to their assets across DeFi whenever necessary.

***

## **Asset Lockdown System**

The **Asset Lockdown System** is an additional layer of security and control that complements the token approval mechanism. It is an opt-in feature with customizable settings to protect users' assets.&#x20;

### **Features of the Cooldown System**

* **Opt-In Mechanism**: Users can choose whether to activate the cooldown system based on their preferences and security needs.
* **Customizable Cooldown Timers**: Users can set specific unlock cooldown durations to limit the immediate transferability of deposited tokens or balances.
* **Transfer Control**:
  * **On/Off Toggle**: Users can enable or disable the ability to transfer deposited tokens and Universal Account Balance funds.
  * If the cooldown timer is decreased, it automatically applies a cooldown to transfers as an added safety precaution.
* **Plugin System Integration**: The cooldown system extends to the plugin system, with a separate on/off toggle for plugin interactions, ensuring comprehensive control across the platform.

***

## **Why It Matters**

The token approval and cooldown systems in Curvance are designed to provide a balance between flexibility and security:

* **Flexibility**: Infinite approvals and plugin integration enable seamless and efficient DeFi interactions.
* **Transparency**: Users can easily monitor and manage approvals, ensuring clarity over asset permissions.
* **Security**: The cooldown system and approval revocation ensure users can limit risks associated with token interactions and unauthorized transfers.

By offering these features, Curvance empowers users to maintain full control over their assets while enjoying a streamlined and secure DeFi experience.


# Lending Risks

## Risk Factors In Decentralized Lending Protocols

As with any DeFi platform, using the Curvance protocol has inherent risks. Users need to understand these risks and manage them effectively.

**1. Liquidation Risk**

Borrowing on Curvance's lending markets exposes users to liquidation risk. The more assets borrowed, the higher the risk of liquidation if collateral values drop. Users are responsible for monitoring and managing their borrow positions. For detailed information, see the [Liquidations](/app/protocol-overview/liquidity-markets/liquidations) section.

**2. Smart Contract Risk**

The protocol is comprised of various open-source smart contracts, which can contain vulnerabilities. Although the protocol undergoes regular audits to minimize this risk, no audit can entirely prevent potential exploits. Additionally, Curvance’s integrations with infrastructure providers and other DeFi protocols introduce added layers of smart contract risk. While auditors vet all protocols in use, users should conduct their own research and assess risks before interacting with any dApp.

**3. Price Data Risk**

Curvance relies on external price data to value collateral, debt, and liquidation conditions. PriceGuards help protect markets by checking whether asset prices move outside expected bounds or become unreliable.

These protections are designed to reduce the impact of bad, stale, or abnormal pricing, but they cannot remove all pricing risk. Fast market moves, thin liquidity, infrastructure outages, or unusual trading conditions may still affect borrowing power, liquidations, and market behavior.


# Contract Addresses (old)

## Monad Mainnet Contract Addresses

### Architecture

<table><thead><tr><th width="169">Contract</th><th>address</th></tr></thead><tbody><tr><td>CentralRegistry</td><td>0x1310f352f1389969Ece6741671c4B919523912fF</td></tr><tr><td>DAOTimelock</td><td>0x2677738657F27e1A3591E00AD7E5a78807688C08</td></tr></tbody></table>

### Market

#### LST Markets

**AprMON / WMON**

{% tabs %}
{% tab title="Market Manager" %}
caprMON / cWMON Market Manager:

```
0x5EA0a1Cf3501C954b64902c5e92100b8A2CaB1Ac
```

{% endtab %}

{% tab title="cTokens" %}
caprMON:

```
0xD9E2025b907E95EcC963A5018f56B87575B4aB26
```

cWMON:

```ada
0xF32B334042DC1EB9732454cc9bc1a06205d184f2
```

{% endtab %}

{% tab title="DynamicIRM" %}
cWMON DynamicIRM:

```
0x0d44480D72B23D19673EEde88aEa03a119bc13f4
```

{% endtab %}

{% tab title="Position Manager" %}
NativePositionManager:&#x20;

```
0x82f88A810E86a730699231F5fBF5686c643467C8
```

SimplePositionManager:

```
0x669786FF3da7544c98FE74eE1006D787118d1770
```

{% endtab %}
{% endtabs %}

**shMON / WMON**

{% tabs %}
{% tab title="Market Manager" %}
cshMON / cWMON Market Manager:

```
0xE1C24B2E93230FBe33d32Ba38ECA3218284143e2
```

{% endtab %}

{% tab title="cTokens" %}
cshMON:

```
0x926C101Cf0a3dE8725Eb24a93E980f9FE34d6230
```

cWMON:&#x20;

```ada
0x0fcEd51b526BfA5619F83d97b54a57e3327eB183
```

{% endtab %}

{% tab title="DynamicIRM" %}
WMON DynamicIRM:

```
0x16cB8A09f2a17E30273b749ed178954FE63bb1BC
```

{% endtab %}

{% tab title="Position Manager" %}
NativePositionManager:&#x20;

```
0x2fAA792502aC5a329DcCc1580C5308fCc0a772fe
```

SimplePositionManager:

```
0x7E8705a164DA7Cc5a4119C750b1F79837AD89D5E
```

{% endtab %}
{% endtabs %}

**sMON / WMON**

{% tabs %}
{% tab title="Market Manager" %}
csMON/ cWMON Market Manager:

```
0xe5970cDB1916B2cCF6185C86C174EEE2d330D05B
```

{% endtab %}

{% tab title="cTokens" %}
csMON:

```
0x494876051B0E85dCe5ecd5822B1aD39b9660c928
```

cWMON:&#x20;

```ada
0xebE45A6ceA7760a71D8e0fa5a0AE80a75320D708
```

{% endtab %}

{% tab title="DynamicIRM" %}
cWMON DynamicIRM:

```
0x16cB8A09f2a17E30273b749ed178954FE63bb1BC
```

{% endtab %}

{% tab title="Position Manager" %}
SimplePositionManager:

```
0x3d6ba6C90E1738cb01C46E72F284C7Bd57eB11EC
```

{% endtab %}
{% endtabs %}

**gMON / WMON**

{% tabs %}
{% tab title="Market Manager" %}
cgMON / cWMON Market Manager:

```
0xb00aFF53a4Df2b4E2f97a3d9ffaDb55564C8E42F
```

{% endtab %}

{% tab title="cTokens" %}
cgMON:

```
0x5ca6966543c0786f547446234492D2F11C82f11f
```

cWMON:&#x20;

```ada
0xf473568b26B8C5aadCa9fbC0eA17E1728d5ec925
```

{% endtab %}

{% tab title="DynamicIRM" %}
WMON DynamicIRM:

```
0x034Cb4152B6506c445f18d564b3Ce86B0DE05C7B
```

{% endtab %}

{% tab title="Position Manager" %}
SimplePositionManager:

```
0x77B90040E6dFE8A62293Cd91CAeE35296EC266Ee
```

{% endtab %}
{% endtabs %}

#### Mu Digital Markets

**muBOND / AUSD**

{% tabs %}
{% tab title="Market Manager" %}
cmuBOND / AUSD Market Manager:

```
0x830D40CDFdc494BC1A2729a7381bfCe44326c944
```

{% endtab %}

{% tab title="cTokens" %}
cmuBOND:

```
0x92EE4b4d33Dc61bd93a88601F29131B08aCedBF1
```

cAUSD:&#x20;

```ada
0x2B4e0232F46E6DB4af35474c140B968EeFCB09Ec
```

{% endtab %}

{% tab title="DynamicIRM" %}
AUSD DynamicIRM:

```
0x08DcB6Ad285217ef29816Ac0F813C21C54c48Cd5
```

{% endtab %}

{% tab title="Position Manager" %}
SimplePositionManager:

```
0xA3d212D4705caf42C35d6EaE0C85E94c4cd329E4
```

{% endtab %}
{% endtabs %}

**loAZND/ AUSD**

{% tabs %}
{% tab title="Market Manager" %}
cloAZND/ AUSD Market Manager:

```
0x7C822B093A116654F824Ec2A35CD23a3749E4f90
```

{% endtab %}

{% tab title="cTokens" %}
cloAZND:

```
0xf7a6AB4aF86966C141D3C5633DF658E5CDb0a735
```

cAUSD:&#x20;

```ada
0xDaDbB2D8f9802DC458F5D7F133D053087Ba8983d
```

{% endtab %}

{% tab title="DynamicIRM" %}
AUSD DynamicIRM:

```
0xf4E19DEdB240FFd9ECEDbfad88A91C34aC3095AA
```

{% endtab %}

{% tab title="Position Manager" %}
SimplePositionManager:

```
0x9e4638fd67bE15360bfebC5e07AECfF33dfda917
```

{% endtab %}
{% endtabs %}

#### Renzo Markets

**ezETH / WETH**

{% tabs %}
{% tab title="Market Manager" %}
cezETH / cWETH Market Manager:

```
0x83840d837E7A3E00bBb0B8501E60E989A8987c37
```

{% endtab %}

{% tab title="cTokens" %}
cezETH:

```
0x20f1A13BfbF85a22Aa59D189861790981372220b
```

cWETH:&#x20;

```ada
0xa206D51C02c0202a2Eed8E6A757b49Ab13930227
```

{% endtab %}

{% tab title="DynamicIRM" %}
cezETH DynamicIRM:

```
0x08DcB6Ad285217ef29816Ac0F813C21C54c48Cd5
```

cWETH DynamicIRM:

```
0xf23d194e151069d09c927E8CD8ae3e14922F2a23
```

{% endtab %}

{% tab title="Position Manager" %}
SimplePositionManager:

```
0xd8d3E46E52C3AB8FD98D04143984C763B4Bbc584
```

{% endtab %}
{% endtabs %}

#### Upshift Markets

**sAUSD / AUSD**

{% tabs %}
{% tab title="Market Manager" %}
csAUSD / cAUSD Market Manager:

```
0xBBE7A3c45aDBb16F6490767b663428c34aA341Eb
```

{% endtab %}

{% tab title="cTokens" %}
csAUSD:

```
0x84C5aF20b58818631164Bb7d798E457fcFACD9Ac
```

cAUSD:&#x20;

```ada
0xfD493ce1A0ae986e09d17004B7E748817a47d73c
```

{% endtab %}

{% tab title="DynamicIRM" %}
cAUSD DynamicIRM:

```
0x22dCF7dd5518F5F08ec05c59606d84698FFF07BB
```

{% endtab %}

{% tab title="Position Manager" %}
VaultPositionManager:

```
0xe94AC990873C3B59A417d25C1ec1398807FB41A1
```

SimplePositionManager:

```
0x491e8712885E4e0b98864F13393ced8c1a61FD57
```

{% endtab %}
{% endtabs %}

**earnAUSD / AUSD**

{% tabs %}
{% tab title="Market Manager" %}
cearnAUSD / cAUSD Market Manager:

```
0xd6365555f6a697C7C295bA741100AA644cE28545
```

{% endtab %}

{% tab title="cTokens" %}
cearnAUSD:

```
0x852FF1EC21D63b405eC431e04AE3AC760e29263D
```

csAUSD:&#x20;

```ada
0xAd4AA2a713fB86FBb6b60dE2aF9E32a11DB6Abf2
```

{% endtab %}

{% tab title="DynamicIRM" %}
cAUSD DynamicIRM:

```
0x46e2bA1D6CFfB180Efa17d4D5A726b599ED8B904
```

{% endtab %}

{% tab title="Position Manager" %}
SimplePositionManager:

```
0xd807efC94376618B4d9CE6eAaf89607006d3B881
```

{% endtab %}
{% endtabs %}

#### Yuzu Markets

**syzUSD/ AUSD**

{% tabs %}
{% tab title="Market Manager" %}
csyzUSD / cAUSD Market Manager:

```
0x90fc1A4abC4ee5f8a9560dE069067D9606d2909C
```

{% endtab %}

{% tab title="cTokens" %}
csyzUSD:

```
0x7EdA3cb060Ff7B650eB227971dbfEBD3513b11D5
```

cAUSD:&#x20;

```ada
0x8E94704607E857eB3E10Bd21D90bf8C1Ecba0452
```

{% endtab %}

{% tab title="DynamicIRM" %}
cAUSD DynamicIRM:

```
0xdCB32fc822eAe8794E48Ad005b50AA850529296D
```

{% endtab %}

{% tab title="Position Manager" %}
SimplePositionManager:

```
0xb233b28fd3c966ebae9D9133DCDD1ad14Cb103B3
```

{% endtab %}
{% endtabs %}

#### Reservoir Markets

**wsrUSD / AUSD**

{% tabs %}
{% tab title="Market Manager" %}
cwsrUSD / cAUSD Market Manager:

```
0x90fc1A4abC4ee5f8a9560dE069067D9606d2909C
```

{% endtab %}

{% tab title="cTokens" %}
cwsrUSD:

```
0x51c82F804df6341373083E42086c75Aa6c18f64f
```

cAUSD:&#x20;

```ada
0xe884C256460044eF86CcC92569765165a7885c7b
```

{% endtab %}

{% tab title="DynamicIRM" %}
cAUSD DynamicIRM:

```
0xB22eaCB543c0a159B0e86c5d2C7FDC162DD8F00B
```

{% endtab %}

{% tab title="Position Manager" %}
SimplePositionManager:

```
0x9bc31b2Ea33bD60fd606Bc2287fF65c3334071f7
```

{% endtab %}
{% endtabs %}

#### DeFi Bluechip Market

**WMON / AUSD**

{% tabs %}
{% tab title="Market Manager" %}
cWMON / cAUSD Market Manager:

```
0xd6365555f6a697C7C295bA741100AA644cE28545
```

{% endtab %}

{% tab title="cTokens" %}
cWMON:

```
0xE01d426B589c7834a5F6B20D7e992A705d3c22ED
```

cAUSD:&#x20;

```ada
0x6E182EB501800C555bd5E662E6D350D627F504D8
```

{% endtab %}

{% tab title="DynamicIRM" %}
cAUSD DynamicIRM:

```
0x72cb6a15E050427e1a6047614A47659A2C94d1bA
```

{% endtab %}

{% tab title="Position Manager" %}
SimplePositionManager:

```
0x2619BB5Acfe51317c4C36a63578FD56148e68C74
```

{% endtab %}
{% endtabs %}

**WMON / USDC**

{% tabs %}
{% tab title="Market Manager" %}
cWMON / cUSDC Market Manager:

```
0xa6A2A92F126b79Ee0804845ee6B52899b4491093
```

{% endtab %}

{% tab title="cTokens" %}
cWMON:

```
0x1e240E30E51491546deC3aF16B0b4EAC8Dd110D4
```

cUSDC:&#x20;

```ada
0x8EE9FC28B8Da872c38A496e9dDB9700bb7261774
```

{% endtab %}

{% tab title="DynamicIRM" %}
cWMON DynamicIRM:

```
0x50264B615175f8DBFFF417CF6D0003D2e77D87B4
```

cUSDC DynamicIRM:

```
0x1622D77E311638C77779e6D9B212E3e7D73AaFB4
```

{% endtab %}

{% tab title="Position Manager" %}
SimplePositionManager:

```
0x960c49E523e6A87282D2bC5032d0AeCb35Dc20ef
```

{% endtab %}
{% endtabs %}

**WBTC / USDC**

{% tabs %}
{% tab title="Market Manager" %}
cWBTC / cUSDC Market Manager:

```
0x01C4a0d396EFE982B1B103BE9910321d34e1aEA9
```

{% endtab %}

{% tab title="cTokens" %}
cWBTC:

```
0x3D2Ff9F862D89Ba526a0fC166bD56ABe04EF28d5
```

cUSDC:&#x20;

```ada
0x7C9d4f1695C6282Da5e5509Aa51fC9fb417C6f1d
```

{% endtab %}

{% tab title="DynamicIRM" %}
cWBTC DynamicIRM:

```
0x8a78Bd450258B7B1D42431fB904453cA43161AdE
```

cUSDC DynamicIRM:

```
0x9F57957F2CA8d0DB433e4623f8bd248293b01e86
```

{% endtab %}

{% tab title="Position Manager" %}
SimplePositionManager:

```
0x8cA51d155C07e91B206Cf11C8D52d5aB082657F6
```

{% endtab %}
{% endtabs %}

**WETH / USDC**

{% tabs %}
{% tab title="Market Manager" %}
cWETH / cUSDC Market Manager:

```
0xb3E9E0134354cc91b7FB9F9d6C3ab0dE7854BB49
```

{% endtab %}

{% tab title="cTokens" %}
cWETH:

```
0x8Af00fbbb2601A8F7636EabbF6243B30BEA47D50
```

cUSDC:&#x20;

```ada
0x21aDBb60a5fB909e7F1fB48aACC4569615CD97b5
```

{% endtab %}

{% tab title="DynamicIRM" %}
cWETH DynamicIRM:

```
0x8c82ccF6Ee43346425E04325Df03219cf46a7739
```

cUSDC DynamicIRM:

```
0x034f1a014946Fe4d4ea3bD36A315F775a8C1aB26
```

{% endtab %}

{% tab title="Position Manager" %}
SimplePositionManager:

```
0x3A1B2Dc11a81Fe106eC667BaB4056fC72498ff73
```

{% endtab %}
{% endtabs %}

### Oracle

#### Oracle Manager

<table><thead><tr><th width="160">Contract</th><th>address</th></tr></thead><tbody><tr><td>Oracle Manager</td><td>0x32faD39e79FAc67f80d1C86CbD1598043e52CDb6</td></tr></tbody></table>

#### Adaptors

<table><thead><tr><th width="219">Contract</th><th>address</th></tr></thead><tbody><tr><td>ChainlinkAdaptor</td><td>0xACfE3fCcae79445836E03c5359BB96bd352b9C00</td></tr><tr><td>RedstoneClassicAdaptor</td><td>0x0fA602b3e748438A3F1599206Ed6DC497ab3331E</td></tr><tr><td>RedstoneCoreAdaptor</td><td>0x1779e22c746b3CA505FfA636F96c4E0916D616d8</td></tr></tbody></table>

### Official Plugins

<table><thead><tr><th width="224">Contract</th><th>address</th></tr></thead><tbody><tr><td>SimpleZapper</td><td>0x91da3583924263ee0da81a06d01946E95FFFB22E</td></tr><tr><td>VaultZapper</td><td>0x9cb6cc5029c23140662636A50c4E8Dc618cC13F1</td></tr><tr><td>NativeVaultZapper</td><td>0xbfE3612D3Db96dc58F2210e2e7FDfe9F4B8c5Ec0</td></tr></tbody></table>

### Views

<table><thead><tr><th width="222">Contract</th><th>address</th></tr></thead><tbody><tr><td>ProtocolReader</td><td>0x878cDfc2F3D96a49A5CbD805FAF4F3080768a6d2</td></tr></tbody></table>


# Monad Contract Addresses

## Markets

### DeFi Bluechip Market

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FtLqEvE6x1YjA3auxh9mv%2Fimage.png?alt=media&amp;token=31905483-9d3e-488e-8e71-91dd784a1c1b" alt="" data-size="line"> WMON | AUSD

<table><thead><tr><th width="235">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0x05e70717fA8BD0F21a9F826d093d99f6Da4f1554</code></td></tr><tr><td>cWMON</td><td><code>0xE01d426B589c7834a5F6B20D7e992A705d3c22ED</code></td></tr><tr><td>cAUSD</td><td><code>0x6E182EB501800C555bd5E662E6D350D627F504D8</code></td></tr><tr><td>cWMON Dynamic IRM</td><td><code>0xc17A271F79ac4449FaABeAf64E805BEE37B78DBd</code></td></tr><tr><td>cAUSD Dynamic IRM</td><td><code>0x72cb6a15E050427e1a6047614A47659A2C94d1bA</code></td></tr><tr><td>SimplePositionManager</td><td><code>0x2619BB5Acfe51317c4C36a63578FD56148e68C74</code></td></tr></tbody></table>

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FrF6x7Q8zcJEjzcYIqm1i%2Fimage.png?alt=media&amp;token=3ab2eafb-b36f-4012-a08e-2d63be8992e2" alt="" data-size="line"> WMON | USDC

<table><thead><tr><th width="231">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0xa6A2A92F126b79Ee0804845ee6B52899b4491093</code></td></tr><tr><td>cWMON</td><td><code>0x1e240E30E51491546deC3aF16B0b4EAC8Dd110D4</code></td></tr><tr><td>cUSDC</td><td><code>0x8EE9FC28B8Da872c38A496e9dDB9700bb7261774</code></td></tr><tr><td>cWMON Dynamic IRM</td><td><code>0x50264B615175f8DBFFF417CF6D0003D2e77D87B4</code></td></tr><tr><td>cUSDC Dynamic IRM</td><td><code>0x1622D77E311638C77779e6D9B212E3e7D73AaFB4</code></td></tr><tr><td>SimplePositionManager</td><td><code>0x960c49E523e6A87282D2bC5032d0AeCb35Dc20ef</code></td></tr></tbody></table>

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FjTlLhTHEUnffhL9wECWI%2Fimage.png?alt=media&amp;token=9e8ba795-5b20-4d54-a07d-03fcdd75801f" alt="" data-size="line"> WBTC | USDC

<table><thead><tr><th width="258">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0x01C4a0d396EFE982B1B103BE9910321d34e1aEA9</code></td></tr><tr><td>cWBTC</td><td><code>0x3D2Ff9F862D89Ba526a0fC166bD56ABe04EF28d5</code></td></tr><tr><td>cUSDC</td><td><code>0x7C9d4f1695C6282Da5e5509Aa51fC9fb417C6f1d</code></td></tr><tr><td>cWBTC Dynamic IRM</td><td><code>0x8a78Bd450258B7B1D42431fB904453cA43161AdE</code></td></tr><tr><td>cUSDC Dynamic IRM</td><td><code>0x9F57957F2CA8d0DB433e4623f8bd248293b01e86</code></td></tr><tr><td>SimplePositionManager</td><td><code>0x8cA51d155C07e91B206Cf11C8D52d5aB082657F6</code></td></tr></tbody></table>

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FtC2dOx3nIQxNm0BxnwRm%2Fimage.png?alt=media&amp;token=78e5ca95-4407-442e-9cda-327f084fdd26" alt="" data-size="line"> WETH | USDC

<table><thead><tr><th width="261">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0xb3E9E0134354cc91b7FB9F9d6C3ab0dE7854BB49</code></td></tr><tr><td>cWETH</td><td><code>0x8Af00fbbb2601A8F7636EabbF6243B30BEA47D50</code></td></tr><tr><td>cUSDC</td><td><code>0x21aDBb60a5fB909e7F1fB48aACC4569615CD97b5</code></td></tr><tr><td>cWETH Dynamic IRM</td><td><code>0x8c82ccF6Ee43346425E04325Df03219cf46a7739</code></td></tr><tr><td>cUSDC Dynamic IRM</td><td><code>0x034f1a014946Fe4d4ea3bD36A315F775a8C1aB26</code></td></tr><tr><td>SimplePositionManager</td><td><code>0x3A1B2Dc11a81Fe106eC667BaB4056fC72498ff73</code></td></tr></tbody></table>

### Renzo Markets

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FNcmNUm3dGlgVxSTsZSfN%2Fimage.png?alt=media&amp;token=2970e465-a8fd-49d5-84ad-c3f116bc7ed5" alt="" data-size="line"> ezETH | WETH

<table><thead><tr><th width="263">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0x83840d837E7A3E00bBb0B8501E60E989A8987c37</code></td></tr><tr><td>cezETH</td><td><code>0x20f1A13BfbF85a22Aa59D189861790981372220b</code></td></tr><tr><td>cWETH</td><td><code>0xa206D51C02c0202a2Eed8E6A757b49Ab13930227</code></td></tr><tr><td>cezETH Dynamic IRM</td><td><code>0x28444Eb9FA9dDA1E6F3D240B2e06a453505e16E6</code></td></tr><tr><td>cWETH Dynamic IRM</td><td><code>0xf23d194e151069d09c927E8CD8ae3e14922F2a23</code></td></tr><tr><td>SimplePositionManager</td><td><code>0xd8d3E46E52C3AB8FD98D04143984C763B4Bbc584</code></td></tr></tbody></table>

### Upshift Markets

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FCCRmPZVU3avLKzHSi1RU%2Fimage.png?alt=media&amp;token=80501b78-5480-43b1-9b26-d7d2c68c3a47" alt="" data-size="line"> sAUSD | AUSD

<table><thead><tr><th width="254">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0xBBE7A3c45aDBb16F6490767b663428c34aA341Eb</code></td></tr><tr><td>csAUSD</td><td><code>0x84C5aF20b58818631164Bb7d798E457fcFACD9Ac</code></td></tr><tr><td>cAUSD</td><td><code>0xfD493ce1A0ae986e09d17004B7E748817a47d73c</code></td></tr><tr><td>csAUSD Dynamic IRM</td><td><code>0xcf31332aB92953101B8E1af7Ca841f6426936da8</code></td></tr><tr><td>cAUSD Dynamic IRM</td><td><code>0x22dCF7dd5518F5F08ec05c59606d84698FFF07BB</code></td></tr><tr><td>VaultPositionManager</td><td><code>0xe94AC990873C3B59A417d25C1ec1398807FB41A1</code></td></tr><tr><td>SimplePositionManager</td><td><code>0x491e8712885E4e0b98864F13393ced8c1a61FD57</code></td></tr></tbody></table>

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2Fj7wRP5k6T79P4vTNL6rd%2Fimage.png?alt=media&amp;token=28205282-0d71-46df-aa7e-a26a9f30a3ed" alt="" data-size="line"> earnAUSD | AUSD

<table><thead><tr><th width="252">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0xd6365555f6a697C7C295bA741100AA644cE28545</code></td></tr><tr><td>cearnAUSD</td><td><code>0x852FF1EC21D63b405eC431e04AE3AC760e29263D</code></td></tr><tr><td>cAUSD</td><td><code>0xAd4AA2a713fB86FBb6b60dE2aF9E32a11DB6Abf2</code></td></tr><tr><td>cearnAUSD Dynamic IRM</td><td><code>0x346339e3Fbd21ed48ADDDE1523896c0d97cF1851</code></td></tr><tr><td>cAUSD Dynamic IRM</td><td><code>0x46e2bA1D6CFfB180Efa17d4D5A726b599ED8B904</code></td></tr><tr><td>SimplePositionManagers</td><td><code>0xd807efC94376618B4d9CE6eAaf89607006d3B881</code></td></tr></tbody></table>

### Yuzu Markets

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FQe0cwI9Yw8LVmBY7NgDY%2Fimage.png?alt=media&amp;token=03829ca7-bed4-4933-a7e2-d3e4c2628bfe" alt="" data-size="line"> syzUSD | AUSD

<table><thead><tr><th width="244">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0x90fc1A4abC4ee5f8a9560dE069067D9606d2909C</code></td></tr><tr><td>csyzUSD</td><td><code>0x7EdA3cb060Ff7B650eB227971dbfEBD3513b11D5</code></td></tr><tr><td>cAUSD</td><td><code>0x8E94704607E857eB3E10Bd21D90bf8C1Ecba0452</code></td></tr><tr><td>csyzUSD Dynamic IRM</td><td><code>0x0A8c317C25A000000296673Bd75cA3f5c1E8C57c</code></td></tr><tr><td>cAUSD Dynamic IRM</td><td><code>0xdCB32fc822eAe8794E48Ad005b50AA850529296D</code></td></tr><tr><td>SimplePositionManager</td><td><code>0xb233b28fd3c966ebae9D9133DCDD1ad14Cb103B3</code></td></tr></tbody></table>

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FKZiUwWeZk4NLdo3gTBAC%2Fimage.png?alt=media&amp;token=570ff8e2-e729-454e-8be0-260e4c39e50b" alt="" data-size="line"> YZM | AUSD

<table><thead><tr><th width="240">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0x4409e2161B6f9A25ACbC471a37390654Ed272249</code></td></tr><tr><td>cYZM</td><td><code>0x8626B8f4F64CAeee9549Af8ebbFA591A7425e5ba</code></td></tr><tr><td>cAUSD</td><td><code>0xcdc9D2c4EaD8f2A9FD3D6F5a00bA4e6001ab7898</code></td></tr><tr><td>cYZM Dynamic IRM</td><td><code>0x9f39f4b61E89f78eE34780664afDc8D5bfAa74ca</code></td></tr><tr><td>cAUSD Dynamic IRM</td><td><code>0xC813497D710CF30f7eEF73eF5dAC53c76fb821E6</code></td></tr><tr><td>SimplePositionManager</td><td><code>0xb85dEf309F7eEEb8f581909c7afB623dDf59AdD9</code></td></tr></tbody></table>

### Reservoir Markets

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FGBs3M2S9lSRRHCkhX3Au%2Fimage.png?alt=media&amp;token=19675ff1-5d16-4972-8b99-3ff9e4c8c5fb" alt="" data-size="line"> wsrUSD | AUSD

<table><thead><tr><th width="235">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0x627a15859d09C1550e2934991AA95aFc6896e3Ef</code></td></tr><tr><td>cwsrUSD</td><td><code>0x251B67Ae7e90fDc6a7B080Ee601913A8B2746A28</code></td></tr><tr><td>cAUSD</td><td><code>0x88e0994E8130EF72bf614CBBcF722839B167c8d1</code></td></tr><tr><td>cwsrUSD Dynamic IRM</td><td><code>0x89ed43d2ab0aE39beE492bA1Ee6117B07aFAA066</code></td></tr><tr><td>cAUSD Dynamic IRM</td><td><code>0xb785f9234d22bA56CCc117CFd3e9f460B8AB354e</code></td></tr><tr><td>SimplePositionManager</td><td><code>0x9bc31b2Ea33bD60fd606Bc2287fF65c3334071f7</code></td></tr></tbody></table>

### Valos Markets

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FQzkKTMpUvLgXwTXJTP7W%2Fimage.png?alt=media&amp;token=46ce5dfb-85b1-409b-ad64-331ea25a62f0" alt="" data-size="line"> vUSD | AUSD

<table><thead><tr><th width="232">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0x2DB9AB1bB00d7000b21A2EbcE7D1a9b91F351E72</code></td></tr><tr><td>cvUSD</td><td><code>0x42369AFe4bA4225b800b8024Acc5F14f42A3836C</code></td></tr><tr><td>cAUSD</td><td><code>0x4806902Ec0320e5334c2B2679FFB58C830348F1c</code></td></tr><tr><td>cvUSD Dynamic IRM</td><td><code>0x6439719873A332AAfb88Bd33e324E597e750c7Bd</code></td></tr><tr><td>cAUSD Dynamic IRM</td><td><code>0xb526Fa1D4e8D680FFDCAcdd977C2c912dD1CA43A</code></td></tr><tr><td>SimplePositionManager</td><td><code>0xCb1709fAF66A73b4f6026b531Bef9AF971368f50</code></td></tr></tbody></table>

### Echo Markets

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FWRCr36Dx6iTQozxXfcIo%2Fimage.png?alt=media&amp;token=d7e1737a-bd51-42c9-8f4d-3e7d8cfe29c6" alt="" data-size="line"> eBTC | WBTC

<table><thead><tr><th width="232">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0xBc4cd7bbd8d38027A88838B88BA04561FA778C35</code></td></tr><tr><td>ceBTC</td><td><code>0x2840772E14fFbe337aB966727B7D1Dd09BDc76E4</code></td></tr><tr><td>cWBTC</td><td><code>0xdB3e888c3b50771821226d30Ab6eC14eB5ba85bA</code></td></tr><tr><td>ceBTC Dynamic IRM</td><td><code>0x2A0Ed67E41662339281A9c9697241d964152B782</code></td></tr><tr><td>cWBTC Dynamic IRM</td><td><code>0x95B961B9aD06dB25831FcdA3c7A79b1EB7c3AFA8</code></td></tr><tr><td>SimplePositionManager</td><td><code>0x5e339724818c4b46E1Bc6966FeB4ea2924Ca17D9</code></td></tr></tbody></table>

### Avant Markets

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FcrX1RdOZHlr0q2KZPcl1%2Fimage.png?alt=media&amp;token=63ddf85a-d660-4146-b6f1-f6fab97487cd" alt="" data-size="line"> savUSD | USDC

<table><thead><tr><th width="236">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0x38104e87Eb96ce5ef1C23a06878dB0D657B4a699</code></td></tr><tr><td>csavUSD</td><td><code>0x3afB9A1cC0d2b0D62502B84B070601fF0DC84363</code></td></tr><tr><td>cUSDC</td><td><code>0x9891178A1178E4C740Fa61Fd6e30A9D92D897590</code></td></tr><tr><td>csavUSD Dynamic IRM</td><td><code>0xa6424210c18C34035204fbd7F44Dcafb04053082</code></td></tr><tr><td>cUSDC Dynamic IRM</td><td><code>0x11EE1eDee565c216f595D329A59863E360DCeB42</code></td></tr><tr><td>SimplePositionManager</td><td><code>0x7B918FF5EDc44B29ced4d3dc52Fb8b859025325c</code></td></tr></tbody></table>

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2F2p5wDvj0PKCFr6bTbs7c%2Fimage.png?alt=media&amp;token=0b5604ad-e814-4430-b52f-591af41d7c37" alt="" data-size="line"> savUSD | AUSD

<table><thead><tr><th width="236">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0x4B0a39eCC3e5A5dA3ce6D492D4D255cD1F0209F7</code></td></tr><tr><td>csavUSD</td><td><code>0x2552232caBd544b67eEa900A951346D3272c7c2f</code></td></tr><tr><td>cAUSD</td><td><code>0xD1BFEA1728ffe98F515f26082fACfcc3341691D4</code></td></tr><tr><td>csavUSD Dynamic IRM</td><td><code>0x402A26Dfd9Df18E384bc5e8d8296EF8fE5AFdee9</code></td></tr><tr><td>cAUSD Dynamic IRM</td><td><code>0x65120989A1f8B1F24b6446a6c08AD1f6BE039Bcf</code></td></tr><tr><td>SimplePositionManager</td><td><code>0xb8F6f480E645dBa96Df9eD3948D900725bA9e9Ec</code></td></tr></tbody></table>

### Mu Digital Markets

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FZ5ekfDkAmAMabcaaw63i%2Fimage.png?alt=media&amp;token=841f2150-f514-4141-9c51-0756faa6586b" alt="" data-size="line"> muBOND | AUSD

<table><thead><tr><th width="232">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0x830D40CDFdc494BC1A2729a7381bfCe44326c944</code></td></tr><tr><td>cmuBOND</td><td><code>0x92EE4b4d33Dc61bd93a88601F29131B08aCedBF1</code></td></tr><tr><td>cAUSD</td><td><code>0x2B4e0232F46E6DB4af35474c140B968EeFCB09Ec</code></td></tr><tr><td>cmuBOND Dynamic IRM</td><td><code>0x5b1d3a91457f7AD854a559dAACc0be18cD402D12</code></td></tr><tr><td>cAUSD Dynamic IRM</td><td><code>0x08DcB6Ad285217ef29816Ac0F813C21C54c48Cd5</code></td></tr><tr><td>SimplePositionManager</td><td><code>0xA3d212D4705caf42C35d6EaE0C85E94c4cd329E4</code></td></tr></tbody></table>

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2F8boR8q8EBhIjdebK1iZM%2Fimage.png?alt=media&amp;token=8a30d4b6-cb6b-4fbc-a380-91b49a5b365f" alt="" data-size="line"> loAZND | AUSD

<table><thead><tr><th width="235">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0x7C822B093A116654F824Ec2A35CD23a3749E4f90</code></td></tr><tr><td>cloAZND</td><td><code>0xf7a6AB4aF86966C141D3C5633DF658E5CDb0a735</code></td></tr><tr><td>cAUSD</td><td><code>0xDaDbB2D8f9802DC458F5D7F133D053087Ba8983d</code></td></tr><tr><td>cloAZND Dynamic IRM</td><td><code>0x5d2b40E3e3FdCDEd15314290d4384D4D12044233</code></td></tr><tr><td>cAUSD Dynamic IRM</td><td><code>0xf4E19DEdB240FFd9ECEDbfad88A91C34aC3095AA</code></td></tr><tr><td>SimplePositionManager</td><td><code>0x9e4638fd67bE15360bfebC5e07AECfF33dfda917</code></td></tr></tbody></table>

### LST Markets

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FQRpONASMzgXQITdLDEe3%2Fimage.png?alt=media&amp;token=ed948303-9797-44dd-bfea-e2c301d72fe6" alt="" data-size="line"> shMON | WMON

<table><thead><tr><th width="246">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0xE1C24B2E93230FBe33d32Ba38ECA3218284143e2</code></td></tr><tr><td>cshMON</td><td><code>0x926C101Cf0a3dE8725Eb24a93E980f9FE34d6230</code></td></tr><tr><td>cWMON</td><td><code>0x0fcEd51b526BfA5619F83d97b54a57e3327eB183</code></td></tr><tr><td>cshMON Dynamic IRM</td><td><code>0x2C802F49fb5ce37D37E947D9b88B2A2Cea7A3C31</code></td></tr><tr><td>cWMON Dynamic IRM</td><td><code>0x963E8aFc1a60E470EB2381896F75853bF6C1FeD2</code></td></tr><tr><td>NativeVaultPositionManager</td><td><code>0x2fAA792502aC5a329DcCc1580C5308fCc0a772fe</code></td></tr><tr><td>SimplePositionManager</td><td><code>0x7E8705a164DA7Cc5a4119C750b1F79837AD89D5E</code></td></tr></tbody></table>

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FTQDfMYl2KpECVkHYAUjU%2Fimage.png?alt=media&amp;token=fee9c862-eb16-4b5b-bd0f-1e15977ef04d" alt="" data-size="line"> aprMON | WMON

<table><thead><tr><th width="227">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0x5EA0a1Cf3501C954b64902c5e92100b8A2CaB1Ac</code></td></tr><tr><td>caprMON</td><td><code>0xD9E2025b907E95EcC963A5018f56B87575B4aB26</code></td></tr><tr><td>cWMON</td><td><code>0xF32B334042DC1EB9732454cc9bc1a06205d184f2</code></td></tr><tr><td>caprMON Dynamic IRM</td><td><code>0x0d44480D72B23D19673EEde88aEa03a119bc13f4</code></td></tr><tr><td>cWMON Dynamic IRM</td><td><code>0x309d475e84b7C99C98031006F491e20280a1A768</code></td></tr><tr><td>NativePositionManager</td><td><code>0x82f88A810E86a730699231F5fBF5686c643467C8</code></td></tr><tr><td>SimplePositionManager</td><td><code>0x669786FF3da7544c98FE74eE1006D787118d1770</code></td></tr></tbody></table>

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2Fe29ZZixRBDyz0G43gyWm%2Fimage.png?alt=media&amp;token=afa1ef7d-46e0-4497-aca9-101c3ad6236b" alt="" data-size="line"> sMON | WMON

<table><thead><tr><th width="213">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0xe5970cDB1916B2cCF6185C86C174EEE2d330D05B</code></td></tr><tr><td>csMON</td><td><code>0x494876051B0E85dCe5ecd5822B1aD39b9660c928</code></td></tr><tr><td>cWMON</td><td><code>0xebE45A6ceA7760a71D8e0fa5a0AE80a75320D708</code></td></tr><tr><td>csMON Dynamic IRM</td><td><code>0x0db363a2eDB3Ac8F4CDdC174ff44B6dfd09A2FeF</code></td></tr><tr><td>cWMON Dynamic IRM</td><td><code>0x16cB8A09f2a17E30273b749ed178954FE63bb1BC</code></td></tr><tr><td>SimplePositionManager</td><td><code>0x3d6ba6C90E1738cb01C46E72F284C7Bd57eB11EC</code></td></tr></tbody></table>

#### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FUrsSixxLARwb5y2hDIXK%2Fimage.png?alt=media&amp;token=fc7b3d5d-9fcc-493b-94c1-9b4a555bd106" alt="" data-size="line"> gMON | WMON

<table><thead><tr><th width="209">Contract</th><th>Address</th></tr></thead><tbody><tr><td>MarketManager</td><td><code>0xb00aFF53a4Df2b4E2f97a3d9ffaDb55564C8E42F</code></td></tr><tr><td>cgMON</td><td><code>0x5ca6966543c0786f547446234492D2F11C82f11f</code></td></tr><tr><td>cWMON</td><td><code>0xf473568b26B8C5aadCa9fbC0eA17E1728d5ec925</code></td></tr><tr><td>cgMON Dynamic IRM</td><td><code>0x95DFba58340Ba7Cde59FB4D16EA85caDb4F83894</code></td></tr><tr><td>cWMON Dynamic IRM</td><td><code>0x034Cb4152B6506c445f18d564b3Ce86B0DE05C7B</code></td></tr><tr><td>SimplePositionManager</td><td><code>0x77B90040E6dFE8A62293Cd91CAeE35296EC266Ee</code></td></tr></tbody></table>

## Earn Vaults

### <img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FcISBbnYGX78gq4LTblYm%2Fimage.png?alt=media&amp;token=2ce93f13-b7a6-4810-a560-ef5b23daf622" alt="" data-size="line"> AUSD

<table><thead><tr><th width="170">Contract</th><th>Address</th></tr></thead><tbody><tr><td>AUSD Flagship</td><td></td></tr></tbody></table>

## Zappers

<table><thead><tr><th width="212">Contract</th><th>Address</th></tr></thead><tbody><tr><td>Native Vault Zapper</td><td><code>0xF562745Ac9bf3466d5eD829819a27Ef0324EF407</code></td></tr><tr><td>Vault Zapper</td><td><code>0x8DA05a1Ad927fFB0A6B0E045b11b94e8d2256366</code></td></tr><tr><td>Simple Zapper</td><td><code>0x7b01E1D9c403aAAF7d960202Fee913F48C43E53c</code></td></tr></tbody></table>

## Architecture

<table><thead><tr><th width="274">Contract</th><th>Address</th></tr></thead><tbody><tr><td>CentralRegistry</td><td><code>0x1310f352f1389969Ece6741671c4B919523912fF</code></td></tr><tr><td>OracleManager</td><td><code>0x32faD39e79FAc67f80d1C86CbD1598043e52CDb6</code></td></tr><tr><td>ProtocolManagerDeployment</td><td><code>0x1482192DeB828a870f7E9B99d75D1DaFda973e4c</code></td></tr><tr><td>DAOTimelock</td><td><code>0x2677738657F27e1A3591E00AD7E5a78807688C08</code></td></tr><tr><td>ProtocolReader</td><td><code>0x55C7c1fe1DACB014aD3b21951728B5E580662268</code></td></tr></tbody></table>

## Oracle Adaptors

<table><thead><tr><th width="283">Contract</th><th>Address</th></tr></thead><tbody><tr><td>Chainlink Adaptor</td><td><code>0xACfE3fCcae79445836E03c5359BB96bd352b9C00</code></td></tr><tr><td>Redstone Classic Adaptor</td><td><code>0x0fA602b3e748438A3F1599206Ed6DC497ab3331E</code></td></tr></tbody></table>


# Token Utility

## **CVE: The Governance Token of Curvance**

The governance token of the Curvance protocol, **CVE**, grants holders the ability to participate in DAO Governance, direct emissions, and influence fee distribution. These governance powers are unlocked by locking CVE tokens to receive **veCVE** via the vote-escrow smart contract.

### **Unlocking Governance Power with veCVE**

Users access voting power by locking CVE for veCVE within the Curvance protocol. The lock period is set for 12 months, with voting power decreasing linearly as the lock approaches its unlock date across each two-week epoch.

* **Governance**: Locking CVE for veCVE enables users to vote on proposals made in the Curvance governance forum, helping shape key parameters and the future direction of the DAO.
* **Direct Emissions**: veCVE holders can vote on biweekly emissions for lending markets, thereby influencing the platform's incentive structure and interest rates.
* **Fee Distribution**: veCVE holders can access a proportional share of platform fees.&#x20;

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FvyoIXwK8HLG5pe7JYZlJ%2Fimage.png?alt=media&amp;token=59b89d12-9728-42de-bf05-51a27c752d7c" alt=""><figcaption></figcaption></figure>

**Boosting veCVE Voting Power**

The Curvance protocol introduces two mechanisms to enhance the voting power of long-term, engaged users, aligning incentives with platform goals:

1. **Continuous Lock**
   * Users can opt for a **continuous lock** to keep their veCVE at maximum lock duration, ensuring consistent voting power without relocking.
   * Users can convert an existing standard lock into a continuous lock at any time, automatically resetting the lock to the maximum lock duration.
   * **Benefits**: Continuous lock users receive a 20% boost to voting power and doubled fee rewards, rewarding long-term DAO participants and frequent platform users.
2. **Bimodal Emissions**
   * When users have unclaimed CVE rewards from gauge emissions, they can choose to lock them immediately as veCVE to receive an additional variable bonus, as determined by the DAO.
   * **Example**: If a user has 100 unclaimed CVE rewards and a claim boost incentive of 20%, opting for the boost would yield 120 veCVE locked for one year.
   * This boost is a one-time option when claiming rewards; boosted tokens can be compounded into an existing continuous lock, added as a new 12-month lock, or compounded into an existing standard lock, resetting the lock duration to 12 months.

{% hint style="info" %}
This is a one-time choice whenever a user has unclaimed rewards. Locking those same rewards after a claim will not yield the bonus veCVE.

The veCVE will always be locked for 12 months whether compounded into a current lock or the creation of a new lock.
{% endhint %}

### **Aligning Long-Term Participation**

These features prioritize long-term participants, providing enhanced rewards and governance power to users who heavily commit to the protocol. This alignment strengthens the vision of becoming the leading DeFi liquidity hub.


# veCVE

The CVE token can be converted to veCVE via the vote-escrow smart contract, which follows a modernized voting escrow model. CVE can be locked up for a period of time (12 months) to gain access to DAO voting and other benefits.

Locking mechanisms inside Curvance vary from conventional methods, allowing for only a single duration of one year. Like many other systems, token voting power decreases linearly as the lock matures. The issue of the necessity to constantly relock to maximize voting power has been addressed with the addition of a "Continuous Lock" mode. This mode allows a user to keep their veCVE position at a full-duration lock that can be enabled or disabled at any time and provides a modest boost to voting power, and a large boost to protocol fees.&#x20;

### Locking CVE for veCVE

By locking CVE as veCVE, a user can access [platform fees](#rewards), [voting power for gauge emissions](/app/cve-token/vecve/curvance-dao), and potentially even [bribes](/app/cve-token/vecve/curvance-dao#bribes). &#x20;

{% hint style="info" %}
**Note:** While on "Continuous Lock," a veCVE position will stay continuously at a full 12-month lock. This can be disabled anytime, allowing the lock timer and voting power to decrease linearly. It can also be re-enabled at any time, which will boost the lock duration back to full length and re-enable the boost to voting power. A user can do this an unlimited number of times.
{% endhint %}

### **Combining Locks**

Users can also opt into combining and condensing their locks; this re-extends the lock back to max duration and cleans up a user's UI/UX interface.&#x20;

### Rewards

Rewards can be distributed proportionally to holders of veCVE. For example, if there are 10,000 veCVE in existence and you own 1,000 of those locked tokens, you would theoretically earn 10% of all fees generated on the platform.&#x20;

{% hint style="info" %}
The above scenario is a general guideline; the reward distribution depends on the number of token holders using continuous locks within Curvance.
{% endhint %}

### veCVE-lock Migration Service

Utilizing Wormhole, the protocol allows users to move their CVE and veCVE tokens from one chain to another.

This has multiple benefits:

* Make use of lower TX fees.
* Make use of faster block times.
* Tokens will never be stuck on one chain.

> **Example:** Timothy locked CVE for veCVE in the continuous lock on Ethereum. He did this at a time when gas prices were low; now gas prices are high.&#x20;
>
> Thanks to the versatility of Curvance, Timothy is able to use the migration service to move his locked veCVE from Ethereum to Arbitrum, where he is able to enjoy lower transaction fees and faster block times.

The migration service allows for flexibility and consistency. Users can choose where to store and use their veCVE based on their preferences, whether for lower transaction fees, better security, or deeper liquidity.


# Curvance DAO

The Curvance Collective

The Curvance DAO is the governing entity of the Curvance protocol and is controlled by the holders of veCVE.

Voting power from veCVE is aggregated from all chains supporting the token and accumulates voting power of all locks across all supported chains. The more veCVE voting power a given user has, the stronger that user's voice will be within the DAO.

The goals are a democratic, transparent, decentralized, strategic decision-maker involving stakeholders and incentivizing participation and contribution.

### Proposals

The DAO can direct structural changes to the protocol through DAO proposals. Any member of the DAO can offer a proposal to improve the Curvance protocol. Proposals may be offered via a public forum to facilitate discussion between token holders and to collect feedback. Once a quorum is reached on the forum, an official proposal and voting process is initiated on Snapshot.

Here are some examples of topics that are related to governance proposals:

* **Expenditures**
* **Treasury**
* **Fees, features, and functionalities**
* **Security**
* **Protocol parameters**
* **Other topics**

### Direct Gauge Emissions

Gauge Emissions allow approved Curvance pools to receive continuously streamed CVE rewards decided by the DAO.

We have explained this model in detail under [Multichain Gauge System](/app/protocol-overview/multichain-architecture/multichain-gauge-system).


# Revenue Model

How does Curvance generate revenue for sustained development and growth?

### **Revenue Sources**

The Curvance protocol generates revenue through various streams that can benefit both **veCVE token holders** and the **DAO**. Each revenue source is designed to enhance the protocol’s sustainability, incentivize participation, and reward stakeholders, creating a balanced ecosystem where both veCVE holders and the DAO contribute to and benefit from protocol growth.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FjI2VqTWasIuJRVTS6pav%2Fimage.png?alt=media&amp;token=e45208ed-ac7c-4d08-b5a2-84709a90f7fe" alt=""><figcaption></figcaption></figure>

### **Revenue Streams to veCVE Holders**

veCVE holders can receive specific revenue streams that align their interests with the protocol's performance, incentivizing their ongoing support and governance participation. Some revenue streams can be distributed as USDC at each epoch, while others provide additional benefits tied to holding voting power in the CVE Gauge System or migrating locks to certain chains:

* **Fee on Underlying Yield from Deposits  (15% currently)**: veCVE holders benefit from a share of the yield generated by deposits, aligning rewards with overall protocol growth.
* **Bribe Rewards**: Revenue from external voting incentivizes that affect CVE Gauge emissions.
* **Partner Protocol Incentives**: Partner protocols can contribute incentives that veCVE holders receive, fostering collaboration and enhancing yield within the Curvance ecosystem.
* **Chain/Network Incentives**: veCVE holders can earn a portion of incentives provided by partner chains and networks, rewarding them for contributing to the protocol's multichain expansion and presence.

### **Revenue Streams to the DAO**

The DAO also benefits from revenue streams that support long-term protocol development, security work, and expansion. These streams help the DAO remain self-sustaining and responsive to the needs of the overall Curvance community:

* **Fee on Active Loan Interest Rates**: The DAO can receive a fee on active loans, enhancing its revenue as lending activities grow.&#x20;
* **Application-Specific Sequencing**: Revenue from selling liquidation order flow will make liquidations in the Curvance protocol highly profitable for the DAO.
* **Fee Switches on Zapping Tools**: A fee switch on native Zapping tools can provide a revenue stream for the DAO, supporting innovative tooling and improvements.
* **Partner Protocol Incentives**: Additional incentives from partner protocols can also benefit the DAO, supporting the protocol’s collaborations and growth initiatives.

### **Alignment of veCVE and DAO Incentives**

The direction of specific revenue streams to veCVE holders and/or the DAO is intended to foster sustainable growth and robust governance within Curvance. By balancing benefits between veCVE token holders and the DAO, an ecosystem where stakeholders are motivated to contribute to and support the protocol’s ongoing success is created.&#x20;


# Emergency Unlock

Access one's CVE liquidity early, for a price

## **Emergency Unlock for veCVE**

For users who have locked their CVE tokens as veCVE, they have access to the **Emergency Unlock** function. This function is available for users who need immediate access to their liquidity before the lock matures. The Emergency Unlock provides a way to unlock veCVE early, subject to a penalty that redistributes forfeited tokens to the DAO treasury.

### **How Emergency Unlock Works**

When a user opts to unlock veCVE early, they incur a penalty on the underlying CVE amount (currently 80%), that gradually decreases as the lock nears maturity.&#x20;

**Examples**:

* If a user locks 1,000 CVE for veCVE and chooses to use the Emergency Unlock immediately, they would receive 200 CVE back while the remaining 800 CVE (80%) is redirected to the DAO treasury as the penalty.
* As the lock duration progresses, the penalty decreases, reaching zero once the full 12-month lock period has completed.

### **Penalty Scaling**

* **Starting Penalty**: The initial penalty rate is configured by the Curvance DAO (currently 80%).
* **Linear Reduction**: The penalty rate decreases linearly as the lock approaches its 12-month term, incentivizing users to hold the lock as intended.

The Emergency Unlock function allows users flexibility while ensuring that early exits contribute to the DAO’s sustainability by replenishing the treasury. This design maintains protocol stability and aligns user incentives with long-term engagement, balancing flexibility with a commitment to the platform’s growth.


# Bug Bounty and Audits

## Security at Curvance

Curvance uses code review, automated testing, independent audits, and a public bug bounty to reduce smart contract risk. These measures do not guarantee that the protocol is free from vulnerabilities.

### **Smart Contract Bug Bounty** <a href="#bug-bounty" id="bug-bounty"></a>

Curvance rewards the first complete, reproducible report of a previously unknown vulnerability that affects an in-scope deployed smart contract and produces an in-scope impact. A report must satisfy both requirements. A suspicious code pattern, tool warning, or hypothetical concern without a reachable production path is not eligible for a bounty.

#### **Assets in scope**

Only Curvance contracts with a populated Monad mainnet address on the [Monad Contract Addresses](/app/protocol-overview/monad-contract-addresses) page and currently in active use are in scope. Blank or placeholder entries, undeployed source, test or mock contracts, and deprecated or inactive deployments are out of scope.

\
The following are **not** in scope unless Curvance lists them expressly:

* Undeployed source code, development branches, tests, mocks, fixtures, and configuration files
* Deprecated contracts that no longer hold funds or affect active positions
* Contracts on unsupported chains or addresses absent from the bounty scope
* Third-party tokens, protocols, bridges, or oracle infrastructure, unless a flaw in Curvance’s integration produces an in-scope impact on a listed Curvance contract
* The website, application, API, and other offchain systems under this smart contract bounty. Reports about those systems may still be sent through the responsible disclosure channel.

#### **Impacts in scope**

A report must demonstrate at least one of the following:

* Direct theft or irreversible loss of user or protocol funds
* Protocol insolvency or unbacked debt
* Permanent freezing of funds
* A material temporary freeze of withdrawals, repayments, liquidations, or another core protocol operation
* Unauthorized minting, burning, transfer, or modification of protocol-accounted assets
* A bypass of collateral, pricing, borrowing, repayment, or liquidation controls that produces one of the impacts above

The presence or absence of a modifier, security pattern, or best-practice control is not an impact by itself.

#### **Required report contents**

Every report must include:

* **Exact target:** Chain, [deployed contract address](/app/protocol-overview/monad-contract-addresses), affected function, and source commit or verified implementation.
* **Attacker model:** Required permissions, starting balances, collateral, approvals, and external dependencies.
* **Complete attack path:** Every call in order, including callbacks, repayments, fees, debt creation, collateral changes, and liquidation steps.
* **Violated invariant:** The property that should hold and the before-and-after state proving that it fails.
* **Net impact:** Attacker profit and protocol or user loss after subtracting required capital, fees, repayments, collateral, and recorded debt. Gross borrowed funds are not profit.
* **Reproduction:** A deterministic test against the source corresponding to the in-scope deployed implementation, or a local fork of that deployment, with the command needed to run it and the expected result.
* **Trace evidence:** Relevant call trace, state changes, balances, and assertion output.

Curvance is not responsible for inferring a missing attack path, repairing a broken proof of concept, or adapting a mock that changes the target’s repayment, authorization, accounting, oracle, or market-manager behavior.

Proofs of concept must run locally. Do not test an exploit against mainnet or a public testnet.

Automated tools and AI may assist research, but their output is not evidence. Scanner or model output that does not satisfy the requirements above will be closed as incomplete.

#### **Safe submission format**

Send the report in the email body as plain text. Do not send executables, macros, disk images, installers, or compressed, encrypted, or password-protected archives. If the minimal source is too long for email, request an approved transfer channel before sending it.

Do not include private keys, credentials, personal data, or data taken from users.

#### **Out of scope and ineligible reports**

The following are not eligible for a bounty:

* Known issues, previously reported issues, and findings already documented in an audit
* Multiple reports sharing the same root cause when one code change resolves them
* Theoretical attacks without a reproducible production path and in-scope impact
* Best-practice recommendations, code-quality observations, gas optimizations, style issues, and feature requests
* Expected rounding, dust, rate movement, liquidation, or other documented economic behavior without demonstrated loss
* Actions available to governance or administrators within their documented authority
* Attacks requiring compromised private keys, leaked credentials, malicious governance approval, phishing, or social engineering
* External asset depegs, incorrect third-party data, or lack of market liquidity unless the reporter demonstrates a Curvance vulnerability that causes an in-scope impact
* Reports that rely on causing damage to users, disrupting production systems, or generating excessive traffic
* Repeated restatements of a closed report without new reproducible evidence

#### **Eligibility and disclosure**

The first eligible reporter is the first person to submit a complete, reproducible report of the distinct root cause, not the first person to send a placeholder or unsupported allegation.

Do not exploit the issue against live contracts, access another person’s data, or move funds that you do not own. Keep the report confidential until Curvance confirms remediation or agrees to a disclosure date in writing.

Curvance will determine whether the report is in scope, whether the issue is reproducible, its severity, and the final reward. The reporter’s proposed severity is not binding. Curvance will explain the technical basis when closing a report as invalid, incomplete, out of scope, known, or duplicate.

A closed report will be reopened only when new evidence changes the attack path, production reach, violated invariant, or demonstrated impact.

#### Rewards

<table><thead><tr><th width="245">Confirmed Severity</th><th width="471">Maximum bounty reward</th></tr></thead><tbody><tr><td>Critical</td><td>$250,000</td></tr><tr><td>High</td><td>$50,000</td></tr><tr><td>Medium</td><td>$5,000</td></tr><tr><td>Low or Informational</td><td>No guaranteed reward</td></tr></tbody></table>

Rewards depend on confirmed impact, funds at risk, exploitability, required attacker capital, and report quality. Reports that do not meet the program requirements are not eligible for payment.

Submit reports to <security@curvance.com>.

### Audits

Audits reduce risk; they do not guarantee that a protocol has no vulnerabilities. Public Curvance reports are available in the `curvance-contracts` [audit directory](https://github.com/curvance/curvance-contracts/tree/develop/contracts/audits):

* TrustSec Audit 2, November 22, 2025
* TrustSec Audit 1, October 17, 2025
* Sherlock Audit, September 29, 2025
* Trail of Bits Audit, May 23, 2025
* Cantina Public Audit, April 16, 2025
* Trail of Bits Invariant Development, March 13, 2024


# Integration Contracts

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FJaikKrzFRjlWaYknR1H8%2FUnder%20Construction.png?alt=media&amp;token=1ccf7d20-ba1a-4f02-8475-f9fcb728eb5a" alt=""><figcaption><p>More information coming soon!</p></figcaption></figure>


# Cantina Bug Bounty

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FJaikKrzFRjlWaYknR1H8%2FUnder%20Construction.png?alt=media&amp;token=1ccf7d20-ba1a-4f02-8475-f9fcb728eb5a" alt=""><figcaption><p>More information coming soon!</p></figcaption></figure>


# RPCs and Testnet Stability

An RPC (Remote Procedure Call) allows users to interact with a blockchain by sending requests to remote servers (nodes) for actions like checking account balances, submitting transactions, or querying blockchain data. It simplifies blockchain interactions by enabling commands to be executed on external servers without requiring direct access to the blockchain infrastructure. This functionality powers wallets, decentralized applications, and other blockchain services, ensuring a seamless user experience.

However, testnet networks are often less stable than their mainnet counterparts. As a result, users may experience issues with certain RPC endpoints. To address these issues, refer to the attached guide for detailed steps on how to update your RPC endpoint and restore functionality.

{% file src="/files/WAwrvUmuojIDmgeDrFr2" %}


# Glossary

<table><thead><tr><th width="287">Term</th><th>Description</th></tr></thead><tbody><tr><td>Application Specific Sequencing</td><td>A method for ordering transactions within an application to optimize functionality and achieve a specific use case.</td></tr><tr><td>Bad Debt</td><td>Occurs when a user's collateral value falls below the amount required to cover a user's outstanding loans.</td></tr><tr><td>Close Factor (cFactor)</td><td>The % amount of a debt position that can be "closed" (repaid) on liquidation. This value scales between the "Base" rate up to 100% on a hard liquidation.</td></tr><tr><td>Collateral</td><td>An asset pledged by a borrower to secure a loan. If the borrower defaults, the collateral can be used to recover the owed debt.</td></tr><tr><td>Collateral Caps</td><td>Collateral caps are limits set which determine the maximum amount of a specific asset that can be supplied as collateral. These caps are designed to manage risk by restricting protocol overexposure to any single asset.</td></tr><tr><td>Collateral Requirement</td><td>The premium of excess collateral required to avoid triggering a soft or hard liquidation.  Part of the formula in the Curvance Dynamic Liquidation Engine.</td></tr><tr><td>Collateralization Ratio</td><td>Defines the maximum borrowing threshold for each asset, reflecting its specific risk profile. Assets with lower risk have higher collateralization ratios. Part of the formula in the Curvance Dynamic Liquidation Engine.</td></tr><tr><td>Price Guard</td><td>A component of Curvance's oracle system where stale or invalid pricefeed values are rejected or clamped.</td></tr><tr><td>Dynamic Interest Rates</td><td>Refers to the Curvance platform's interest rates, which adjust in real-time based on market demand within each lending pool.</td></tr><tr><td>ERC-4337</td><td>ERC-4337 is a standard for smart accounts that enables account abstraction. This standard enhances application-specific sequencing for liquidation auctions and OEV capture.</td></tr><tr><td>ERC-4626</td><td>ERC-4626 is a tokenized vault standard in DeFi designed to enhance interoperability and efficiency for yield-bearing assets. Curvance's native vaults leverage this standard to streamline deposits, auto-compound rewards, and integrate seamlessly with other protocols for maximum capital efficiency.</td></tr><tr><td>Borrowable cTokens</td><td>When users deposit assets into Curvance as lenders, they receive a proportionate amount of Borrowable cTokens, representing their share in the lending pool.</td></tr><tr><td>Hard Liquidation</td><td>Full liquidation with a high penalty if the Health Factor is critically low, meaning Curvance can shed risk faster than other lending protocols in times of high volatility.</td></tr><tr><td>Health Factor</td><td>A numerical representation of the safety of a user's collateralized position. It measures how close a position is to being liquidated.</td></tr><tr><td>Interest</td><td>The fee borrowers pay to lenders for accessing funds</td></tr><tr><td>Liquidation Factor (lFactor)</td><td>The % skew between a soft liquidation and a hard liquidation. This value controls the effective cFactor and liquidation incentive for a user liquidation. A lFactor of 0% indicates a base soft liquidation, an lFactor of 100% indicates a hard liquidation. Anywhere inbetween blends the effective rates.</td></tr><tr><td>Liquidation Fee</td><td>The penalty the protocol takes from a user's collateral during a liquidation. Part of the formula in the Curvance Dynamic Liquidation Engine.</td></tr><tr><td>Liquidation Incentive</td><td>The liquidator's incentive to liquidate a user position. Part of the formula in the Curvance Dynamic Liquidation Engine.</td></tr><tr><td>Loan to Value (LTV)</td><td>Percentage used to represent the relationship between the amount of a loan (debt) and the value of the collateral backing it. It is a key metric for determining how much a user can borrow against their deposited collateral.</td></tr><tr><td>Looping</td><td>A strategy in which a user deposits a collateral asset and borrows funds to purchase more of an asset.</td></tr><tr><td>Oracle</td><td>A service that provides smart contracts with access to external data such as price feeds of assets.</td></tr><tr><td>Orderflow Auctions</td><td>A decentralized offchain auction system for "selling" the opportunity to liquidate user positions inside the Curvance Protocol.</td></tr><tr><td>cToken </td><td>When users deposit tokens into the Curvance Protocol, they receive a proportionate amount of cTokens (Curvance Tokens), representing their share in the managed vault. Earned yield is automatically compounded, increasing the user’s overall position over time.</td></tr><tr><td>Socalized Bad Debt</td><td>Spreading potential shortfalls across the entire lender market. This equitable approach reduces individual exposure, prevents bad debt accumulation, and strengthens the market's overall stability.</td></tr><tr><td>Soft Liquidation</td><td>A partial liquidation occurs with a small penalty, preserving more of the user's collateral compared to traditional full liquidation designs.</td></tr><tr><td>Utilization Rate</td><td>The percentage of available liquidity in a lending or liquidity pool that is actively being borrowed or used.  Pool utilization is part of the formula when dynamic interest rates are calculated.</td></tr></tbody></table>

&#x20;   &#x20;


# TL;DR

For those in a hurry...

**What is Curvance?** Curvance is a decentralized, multichain liquidity hub that empowers users to unlock the full potential of their digital assets.

**Key Features:**

* Secure, modular, and composable protocol supporting any ERC-20 token
* Chain-agnostic reward and utility layer for yield-bearing assets
* ERC-4626 vault technology for integration with third-party strategies and DeFi flywheels
* Multichain equivalence powered by Wormhole for access to opportunities across ecosystems
* Custom-built liquidation engine and cross-chain voting for an improved user experience

**Benefits:**

* Unlock liquidity and maximize yields across multiple chains
* Simplify DeFi with a seamless, trustless user experience
* Participate in a sustainable DAO model with decentralized governance
* Discover new yield-generating opportunities and innovative DeFi products

**Join the Curvance Community:** Learn more about Curvance and get involved in shaping the future of DeFi. Visit the website, X, Telegram, and Discord channels to stay updated and join the conversation.

Website: <https://curvance.com/>

X: <https://x.com/Curvance>

Telegram: <https://t.me/curvance>

Discord: <https://discord.com/invite/curvance>


# User Types and Benefits

Who is Curvance designed for, and how do those people benefit from it's existence?

The Curvance protocol empowers a wide range of DeFi users, each with specific needs. By delivering tailored solutions to each customer type, the platform drives growth, capital efficiency, and revenue generation through diverse mechanisms.

### **Individual Users (Retail)**

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FPNqMqwCEIL4nj9fEgTww%2F2.jpg?alt=media&amp;token=d0d83172-c1b1-498e-9300-7c9ad04ea230" alt=""><figcaption></figcaption></figure>

**Why They Use Curvance:**\
Retail users seek accessible, simplified ways to maximize yields and manage their assets across multiple chains.

**How Curvance Benefits Them:**

* Unified position management tooling for an easy, seamless DeFi experience.
* Access to optimized yield strategies, enhancing yield on deposits.
* Collateralized loans and one-click leverage that improve financial flexibility.
* Visibility into top opportunities across chains, supporting well-informed investment decisions.

### **Token Issuers / Treasury Managers**

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FRM3cRJlPuzgqmYMjH51J%2F4.jpg?alt=media&amp;token=8d31ddee-6f57-4d9f-a035-2de5c1c4fd4a" alt=""><figcaption></figcaption></figure>

**Why They Use Curvance:**\
Token issuers and Treasury Managers seek to maximize treasury performance, liquidity management efficiency, and liquidity depth for their tokens.

**How Curvance Benefits Them:**

* Auto-compounding of LP tokens to optimize yields on protocol-owned assets and flywheel bribing.
* Increased ROI via the CVE Gauge System, stacking on top of yield from underlying strategies.
* Access to collateralized loans to enhance treasury efficiency.
* Flexible veCVE tokens are adaptable to existing flywheels, providing additional flexibility when expanding to new networks.

### **Institutional Investors (Liquid Funds)**

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FWFMLFZNI2itDs2x30Ry8%2F3.jpg?alt=media&amp;token=d0775b38-3efb-4275-b06a-87119c065fc5" alt=""><figcaption></figcaption></figure>

**Why They Use Curvance:**\
Institutional investors can use the Curvance platform for custom-built strategies, capital efficiency for their existing liquidity provisioning deals, and peace of mind with permissioned pools, which align with their overall compliance requirements.

**How Curvance Benefits Them:**

* Access to customized structured products tailored to institutional needs.
* Compliance infrastructure integrations ensure that strategies align with regulatory standards.
* Access to spot leverage maximizes investment power and potential returns.
* Enhanced capital efficiency via optimized strategies designed for high liquidity.

### **Chains / Networks**

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FdpzIXELLw0a1MwJ2Pc9C%2F5.jpg?alt=media&amp;token=2d76b631-a749-4d53-a900-9374e3c8840b" alt=""><figcaption></figcaption></figure>

**Why They Use Curvance:**\
Chains and networks can leverage the platform to drive ecosystem growth, increase TVL (total value locked), and foster a vibrant DeFi environment.

**How Curvance Benefits Them:**

* Predictable, measurable TVL inflows, bolstering network activity and liquidity.
* Enhanced transaction volumes, promoting sequencer revenue.
* Marketing and ecosystem exposure, amplifying their reach and user base.
* A multichain vote-escrow system enables networks to "siphon" liquidity from other chains.


# Brand Assets

**Curvance brand assets can be found** [**here**](https://www.dropbox.com/scl/fo/sfb76gqtg34u4g0w9jtsk/h?rlkey=0vi2f7akyobsa0g5o94ykotlv\&dl=0)**.**


# Weblinks

## Official Links

* [**Website**](https://www.curvance.com/)
* [**Discord**](https://discord.gg/curvance)
* [**Twitter**](https://twitter.com/Curvance)
* [**Telegram**](https://t.me/CurvanceChat)
* [**Farcaster**](https://warpcast.com/curvance)


# Disclaimer

This documentation is a rough draft and currently undergoing legal review for compliance, and as a result, are subject to change.


# Frequently Asked Questions

**1. Does Curvance expose users to bridge risk?**

No, Curvance does not expose users to bridge risk in lending positions or vault strategies. Vault deposits and lending positions remain on their respective chains, and users are never forced to bridge assets. Bridging via Wormhole is utilized when a user uses a crosschain plugin to migrate liquidity across chains.

**2. How does Curvance optimize yield compared to other DeFi protocols?**

Curvance auto-compounds yield-bearing assets and routes liquidity through additional reward layers, ensuring maximum yield efficiency. Unlike traditional lending protocols, Curvance allows collateralized assets to continue earning while being used within DeFi strategies.

**3. What makes Curvance’s lending markets unique?**

Curvance features risk-isolated, intent-based lending markets, blending elements of shared pools and isolated markets. This enables flexible risk management, higher LTVs for safer assets, and innovative lending opportunities tailored to different DeFi strategies.

**4. How does Universal Balance benefit protocols and users?**

Universal Balance lets protocols passively earn a yield on idle assets without disrupting the user experience. Users benefit by earning passive rewards while retaining full control over their funds.  While protocols can integrate native yield generation with minimal friction.

**5. How does the Plugin System improve composability?**

Curvance’s Plugin System allows developers to extend protocol functionality with custom integrations, automated strategies, and third-party applications. It enhances composability by enabling seamless yield routing, lending automation, and governance tools—all within Curvance.

**6. What makes Curvance’s vote-escrow system different?**

Curvance’s multichain vote-escrow system removes liquidity fragmentation present in all other traditional vote-escrow models.  Allowing native token lockers to direct platform emissions to vaults on any supported chain. Protocol fees are distributed pro-rata across all chains, ensuring efficient capital flow.

**7. Is Curvance non-custodial?**

Yes. Curvance is fully non-custodial, meaning users always maintain control of their assets. Funds are never held by Curvance, and users interact with permissionless smart contracts for deposits, lending, and withdrawals.

**8. How does Curvance handle liquidations?**

Curvance leverages MEV-optimized liquidations to minimize costs for borrowers while ensuring market stability. With dynamic liquidation incentives and order flow auctions (OFAs), Curvance enhances efficiency and reduces slippage for liquidated positions.

**9. What security measures are in place?**

Curvance prioritizes security with:

* **Dual-oracle protection** to prevent price manipulation.
* **Circuit breakers** to halt abnormal market activity detected by the Dual Oracle system.
* **Third-party audits** from leading smart contract security firms such as: Spearbit, Cantina, Trail of Bits, Trust Security, and yAudit.
* **Collateral caps and risk-adjusted lending pools** to mitigate systemic risks.

**10. What types of assets does Curvance support?**

Curvance supports both yield-bearing and non-yield-bearing assets, including LSTs, LRTs, stablecoins, LP tokens, and popular assets like WETH and WBTC. Yield-bearing assets continue generating rewards even when used as collateral, while non-yielding assets benefit from enhanced capital efficiency through lending, borrowing, and liquidity incentives.


# Privacy Policy

PRIVACY POLICY

This Privacy Policy (“Policy”) is effective as of November 22, 2025, and outlines how Curvance Platform Inc., a company incorporated under the laws of the Republic of Panama (“we”, “us”, or “our”), collects, uses, shares, and protects personal information of users (“you” or “users”) of our Curvance platform, including any website or application on which this Policy appears (the “Platform”). By accessing or using our Platform, you agree to the terms of this Policy.

1. Introduction and Notice at Collection

This section elaborates on the scope and applicability of this Policy. The “Platform” refers to our online crypto lending and yield generation platform. As a company incorporated and operating under the laws of the Republic of Panama, we are committed to protecting your privacy while providing our services.

We collect, and in the past 12 months have collected, the following categories of information from and about you: identifiers; commercial information; internet or similar network activity; government-issued identifiers; financial account numbers; and inferences drawn from other personal information. We collect these categories of information from you in order to provide our services to you, to run our business, to communicate with you, and as required by law. We may have disclosed each of these categories of personal information for a business purpose as described below. We have not “sold” or “shared” personal information in the past 12 months, as those terms are defined by the CCPA.

We will retain each of these categories of personal information based on several factors, including as reasonably necessary to (i) provide our products and services or administer our relationship with a consumer; (ii) protect our business, employees, organization, and others; (iii) fulfill our legal and regulatory obligations; and (iv) investigate and address issues which may include safety concerns, potential security incidents or policy violations.

This Policy governs all aspects of information collection, processing, and protection across our Platform’s services, including but not limited to account creation, market participation, transaction processing, and user interactions. It applies to all users accessing our Platform, whether as registered members or visitors.

By using our Platform, you acknowledge that you have read and understood this Policy and agree to be bound by its terms. If you do not agree with any aspect of this Policy, you should immediately discontinue use of our Platform. We recommend reviewing this Policy periodically as it may be updated from time to time in accordance with Section 9 of this document.

For purposes of this Policy, any capitalized terms not defined herein shall have the meanings assigned to them in our Terms of Service. This Policy should be read in conjunction with our Terms of Service and any other agreements between you and us governing your use of the Platform.

We are aware of our responsibilities to handle your personal data with care, to keep it secure, and comply with privacy and data protection laws applicable to our company and your personal information, including Law 81 of March 26, 2019, on the Protection of Personal Data and its Regulations, the Executive Decree 285 of May 28, 2021 ("Data Protection Laws").

1. Information We Collect

We collect and process two main categories of information about you when you use our Platform:

“Personal Information” refers to information that can directly or indirectly identify you as an individual. This includes your full name, email address, postal address, phone number, date of birth, government-issued identification documents, banking and financial information, and account login credentials. We may collect some or all of this information when you use the Platform, make transactions, or otherwise communicate or interact with us in any way.

“Usage Data” encompasses information about how you interact with our Platform. This includes your IP address, browser type and version, device information, operating system, time zone setting, access times and duration, pages viewed, features used, transaction history,  and other diagnostic data. We, or our third-party vendors, collect this information automatically when you access and navigate our Platform through cookies, pixels, web beacons, and similar tracking technologies.

We maintain detailed records of all Platform transactions, including stakes placed, outcomes, and associated timestamps. Your IP address and device identifiers are logged for security purposes and to comply with our legal obligations regarding market integrity and fraud prevention.

Please note that certain features of the Platform may not be accessible if you choose not to provide some of the requested information. Any personal information you provide will be processed in accordance with this Policy and applicable data protection laws.

2.1. Consent for Information Collection and Processing

In accordance with the Data Protection Law, by using the Platform you explicitly consent to us collecting, using and/or processing your Personal Information for:

(a) Collection and processing of basic account information necessary for Platform functionality;

(b) Processing of identity verification documents as required by applicable laws;

(c) Use of your financial information for transaction processing;&#x20;

(d) Collection and analysis of Usage Data to improve our services; and

(e) Sharing your information with third parties as outlined in Section 4 of this Policy.

Where we collect sensitive personal data (including but not limited to government-issued identification documents, or financial information), we may implement additional security measures and obtain separate explicit consent for such collection and processing.

You have the right to withdraw your consent at any time by contacting us. Please note that withdrawal of consent for certain essential processing activities may impact our ability to provide you with access to some or all of the Platform features.

We maintain records of all consent provided by users in accordance with the Data Protection Law and make these records available to regulatory authorities upon legitimate request.

1. How We Use Your Information

We process the personal information we collect about you for the following purposes:

* Account Management and Platform Operations: We use your information to create and maintain your account, process your transactions, provide customer support, and ensure the proper functioning of our prediction market platform. This includes verifying your identity, processing payments, and communicating with you about your account activities.
* Platform Improvement and Analytics: We analyze user behavior and platform performance to enhance our services, develop new features, and optimize user experience. This includes conducting statistical analyses, performing market research, and generating aggregated insights about platform usage patterns. Such processing is conducted using industry-standard analytics tools with appropriate safeguards in place.
* Marketing and Communications: We use your information to send you promotional materials, newsletters, product updates, and other marketing communications that may be of interest to you. This includes personalized recommendations based on your Platform activity and preferences. You may opt out of marketing communications at any time as described in in this Policy.
* Legal Compliance and Risk Management: We process your information to comply with applicable laws, regulations, and legal obligations, including anti-money laundering requirements, tax reporting, and response to legal requests from authorities. We also use your information to detect and prevent fraud, unauthorized access, and other prohibited activities on our Platform.

The processing of your personal information is necessary for the performance of our contract with you, compliance with our legal obligations, and our legitimate interests in operating, improving, and protecting our Platform. We retain your information only for as long as necessary to fulfill these purposes or as required by applicable law.

1. Information Sharing and Disclosure

We may share your personal information with third parties and service providers in the following circumstances:

* With our affiliated companies and subsidiaries who assist in operating our Platform and providing services to you, subject to confidentiality obligations consistent with this Policy. These affiliates may be located in jurisdictions outside Panama.
* With third-party service providers who help us operate, maintain, and enhance our Platform, including payment processors, cloud storage providers, analytics services, and customer support services.&#x20;
* When required by law, regulation, or legal process, including to respond to court orders, subpoenas, or government requests. We may also share information when we believe disclosure is necessary to protect our rights, investigate fraud, or enforce our Platform’s terms of service.
* In connection with a merger, acquisition, bankruptcy, dissolution, reorganization, or similar corporate event. If we are involved in such a transaction, you will be notified via email and/or a prominent notice on our Platform of any change in ownership or uses of your personal information.

We do not sell, rent, or lease your personal information to third parties for their marketing purposes without your explicit consent.&#x20;

1. Data Security

We implement and maintain appropriate technical and organizational security measures to protect your Personal Information from unauthorized access, disclosure, alteration, and destruction. These measures include industry-standard encryption protocols for data in transit and at rest, secure socket layer (SSL) technology, firewalls, and restricted access controls to our data centers and information systems.

Access to Personal Information is strictly limited to authorized employees, agents, and service providers who require such access to perform their assigned duties. These individuals are bound by confidentiality obligations and may be subject to discipline, including termination and criminal prosecution, if they fail to meet these obligations.

While we strive to use commercially acceptable means to protect your Personal Information, we cannot guarantee its absolute security. Any transmission of personal information to and from our Platform is at your own risk.

1. Your Rights and Choices

Depending on your state or country of residence, you may have certain rights regarding your personal information that we collect and process. You may exercise these rights by contacting our Data Protection Officer using the contact information provided in Section 10 of this Policy.

Your rights include: (a) the right to access your personal information and request details about how we process it; (b) the right to request correction of inaccurate personal information; (c) the right to request deletion of your personal information, subject to our legal obligations and legitimate business interests; (d) the right to object to or restrict certain processing activities; (e) the right to data portability; (f) the right to limit the use of disclose of your sensitive personal information; and (g) the right to opt out of the sale or sharing of your personal information.

Please note that we do not collect or process sensitive personal information, as defined by applicable Data Protection Laws, except as necessary to perform our services. We also do not knowingly “sell” or “share” any personal information, including any personal information relating to consumers under the age of 16, as we understand those terms to be defined under applicable Data Protection Law.

Agents that you have authorized to act on your behalf may also submit requests as instructed below. The agent must also provide evidence that they have your written permission to submit a request on your behalf. If we are unable to verify the authenticity of a request, we may ask you for more information or may deny the request.

You may opt-out of receiving marketing communications from us at any time by: (i) clicking the “unsubscribe” link in any marketing email we send; or (ii) contacting our Data Protection Officer. Please note that even if you opt-out of marketing communications, we may still send you important Platform-related notifications and updates.

If you wish to delete your account, you may do so through your account settings or by contacting us. Please note that some information may be retained in our records to comply with legal obligations, resolve disputes, enforce our agreements, or protect our legitimate business interests.

We will respond to all legitimate requests within ten (10) business days from the date of submission. For requests to modify data, we will process these within five (5) business days.

If you would like to adjust your cookie settings, you can adjust your browser to control cookies to require your affirmative acceptance for new cookies, automatically reject cookies and/or delete or disable existing cookies. How you do so depends on the type of cookie and the browser that you are using. For more information on how to control browser cookies, [click here](https://thenai.org/self-regulatory-framework/faq/). If information about your browser is not available through this page, please search your browser for instructions about how to opt-out of cookie collection.&#x20;

Some browsers have “do not track” features that allow you to tell a website not to track you. These features are not all uniform. We do not currently respond to those signals.  If you block cookies, certain features on our sites may not work.  If you block or reject cookies, not all of the tracking described here will stop.

1. International Data Transfers

We may transfer, store, and process your personal information in countries other than Panama. By using the Platform, you acknowledge and consent that your personal information may be transferred to and processed in countries that may have different data protection rules than your country of residence.

For transfers of personal data to countries outside the European Economic Area (EEA), we implement appropriate safeguards and mechanisms to ensure adequate protection of your personal information. These safeguards include entering into Standard Contractual Clauses approved by the European Commission with our service providers and partners.

We maintain appropriate technical and organizational measures to protect your personal information during international transfers, including encryption of data in transit and storage. Where we engage third-party service providers who may access your personal information from other countries, we require them to provide at least the same level of privacy protection as required under applicable data protection laws.

If you are located in the European Union, you have the right to obtain information about the safeguards we use for transferring your personal information outside the EEA. You may contact our Data Protection Officer using the contact information provided in Section 10 to request such information.

1. Children’s Privacy

The Platform is not directed to, designed for, or intentionally targeted at children under the age of 18. We do not knowingly collect personal information from children under 18 years of age. If you are under 18, you may not use or provide any information on the Platform, register an account, make any predictions, or engage in any transactions.

If we become aware that we have inadvertently collected personal information from a child under the age of 18, we will take steps to delete such information from our records as soon as possible. If you believe we might have any information from or about a child under 18, please contact our Data Protection Officer immediately at the contact information provided in Section 10 of this Policy.

Parents and legal guardians who believe their child has submitted personal information to our Platform without their consent should contact us to request the deletion of that information. We reserve the right to request proof of identity and/or legal guardianship to process such requests.

1. Changes to This Policy

We reserve the right to update or modify this Policy at any time and from time to time without prior notice. We will post any revised version of the Policy on the Platform and update the “effective date” referenced at the beginning of this Policy. If we make any material changes to this Policy, we will notify you through a prominent notice on our Platform, by sending you an email to the last email address you provided to us, or through other appropriate communication channels.

Your continued use of the Platform following the posting of changes to this Policy will constitute your acknowledgment and acceptance of such changes. We encourage you to periodically review this Policy to stay informed about how we collect, use, and protect your personal information. If you do not agree with any changes to this Policy, you must discontinue using our Platform.

Any changes or modifications will be effective immediately upon posting the revised Policy on the Platform. These changes will apply to all information we have about you, as well as any information we may receive in the future.

1. Contact Us

For any questions, concerns, or requests related to your personal information or this Policy, you may contact us through the website.

We will endeavor to respond to your inquiries within 10 days of receipt. If you are exercising any of your data protection rights, we may need to request specific information from you to verify your identity. This is a security measure to ensure that personal information is not disclosed to unauthorized persons.

You may exercise your rights of access, rectification, cancellation, or opposition (“ARCO Rights”) concerning your personal data, as well as request the withdraw of consent, by sending a request through the website.&#x20;

1. Governing Law and Dispute Resolution

This Policy shall be governed by and construed in accordance with the laws of the Republic of Panama, without regard to its conflict of law provisions. To the extent permitted by applicable law, any dispute, controversy or claim arising out of or relating to this Policy, or the breach, termination or invalidity thereof, shall be settled by binding arbitration in Panama City, Panama, in accordance with the Rules of Arbitration of the International Chamber of Commerce by one or more arbitrators appointed in accordance with said Rules.

The arbitration shall be conducted in the English language. The arbitral tribunal shall consist of three arbitrators, with each party appointing one arbitrator and the two party-appointed arbitrators selecting the third arbitrator who shall serve as the presiding arbitrator. The award rendered by the arbitrators shall be final and binding on the parties, and judgment upon the award may be entered in any court having jurisdiction thereof.

Nothing in this Policy shall limit your rights under the Data Protection Law, including your right to file complaints with the National Authority for Transparency and Access to Information (ANTAI) for matters related to personal data protection. However, disputes arising from other contractual matters shall be resolved through arbitration.&#x20;


# Overview

Welcome to the official technical documentation for Curvance, a protocol designed to provide capital-efficient money market services with advanced risk management and MEV capture capabilities. This documentation serves as the comprehensive guide for developers, integrators, and auditors working with the Curvance smart contract system.

## Documentation Structure

Unlike traditional contract-centric documentation, **this documentation is organized by function** rather than by individual contracts. This approach offers several advantages:

* **Workflow-Based Navigation:** Follow complete processes from start to finish.
* **Contextual Understanding:** See how functions interact across multiple contracts.
* **Use-Case Orientation:** Find solutions based on what you're trying to accomplish.

For example, instead of separate pages for `CentralRegistry.sol` or `LiquidityManager.sol`, you'll find integrated explanations of liquidation workflows that span multiple contracts.

<table data-view="cards"><thead><tr><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="image">Cover image</th></tr></thead><tbody><tr><td>Get started integrating Curvance into your platform</td><td><a href="/app/developer-docs/quick-start-guides">Quick Start Guides</a></td><td><a href="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FyJw5ad74bORLzWtdK3Qw%2F0b17ffa1-cf4d-4654-861d-6265b84a7f06.png?alt=media&amp;token=2a72965a-5fde-49ae-8dbd-051522129a9f">0b17ffa1-cf4d-4654-861d-6265b84a7f06.png</a></td></tr><tr><td>Learn about our Lending Protocol</td><td><a href="/app/developer-docs/lending-protocol">Lending Protocol</a></td><td><a href="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FhN5rKaD3s3W3J3dwLXnG%2Fd4facaf1-797b-40e0-89c6-59b04dc33f51.png?alt=media&amp;token=8827083c-5847-49d0-b315-d4a08b70d9de">d4facaf1-797b-40e0-89c6-59b04dc33f51.png</a></td></tr><tr><td>Learn about our leverage system</td><td><a href="/app/developer-docs/position-management">Position Management</a></td><td><a href="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2Fa7pqWMt61QB3RUHrkL5C%2FChatGPT%20Image%20May%204%2C%202026%2C%2002_45_52%20PM.png?alt=media&amp;token=f0ae5385-bcd9-4843-a203-2b35abe811fc">ChatGPT Image May 4, 2026, 02_45_52 PM.png</a></td></tr><tr><td>Learn about our liquidation engine</td><td><a href="/app/developer-docs/dynamic-liquidation-engine-dle">Dynamic Liquidation Engine (DLE)</a></td><td><a href="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FEmBibxCRnx5QqbWi0VDr%2F06baea46-d6a5-44c4-a1c1-7efd96407ef5.png?alt=media&amp;token=3d00b4d3-f7b4-4ee2-855b-1f3c18f7676e">06baea46-d6a5-44c4-a1c1-7efd96407ef5.png</a></td></tr><tr><td>Learn about our plugin and delegation system</td><td><a href="/app/developer-docs/plugin-and-delegation-system">Plugin &amp; Delegation System</a></td><td><a href="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FPUxJ7YhqZOLQbTfPAuVZ%2Fadfd3a56-4ef0-4b96-8ffd-001e3d34a8d8.png?alt=media&amp;token=f6a1c378-4aa7-47d1-a664-94545f4665e9">adfd3a56-4ef0-4b96-8ffd-001e3d34a8d8.png</a></td></tr></tbody></table>


# SDK

The Curvance contract SDK is the TypeScript SDK for building applications, scripts, and integrations on top of the Curvance protocol. It wraps Curvance contract deployments, ABIs, market metadata, token models, protocol readers, zappers, leverage helpers, and external integrations behind a developer-friendly ethers v6 interface.

The SDK is designed to make protocol reads cheap and ergonomic. setupChain() bulk-loads market and protocol state up front, then exposes cached Market, CToken, BorrowableCToken, ProtocolReader, and DEX aggregation helpers for common workflows like market discovery, balance checks, position health, deposits, borrowing, repayment, zaps, and leverage planning.

Github: <https://github.com/curvance-web/sdk/tree/master>


# Quick Start Guides

These quick start guides will help you integrate with Curvance's smart contracts using our official API. Whether you're building a frontend dApp, creating a trading bot, or integrating Curvance into your own protocol, these guides provide the core code snippets and explanations you need.

### Overview

Curvance is a money market protocol that enables users to deposit collateral, borrow assets, and participate in governance. The protocol features:

* Isolated risk environments for different asset classes.
* Dynamic Liquidation Engine (DLE) for efficient risk management.
* Optimized gas usage through innovative design patterns.
* MEV (Maximum Extractable Value) capture through Atlas Fastlane auctions.

### Prerequisites

To follow these guides, you'll need:

* Node.js environment.
* Basic knowledge of JavaScript and Ethereum.

### Guide Contents

These quick start guides cover the following areas:

1. Integration Cookbook
2. Loans & Collateral
3. Borrowing & Repayment
4. Leverage
5. Plugin Integration
6. Liquidations

Each guide includes complete code examples, transaction parameter explanations, and tips for error handling and gas optimization. Follow along with these guides to build powerful DeFi applications on top of the Curvance protocol.\
\
Let's get started with building on Curvance!


# Integration Cookbook

## Overview

This page provides walkthroughs for various integration scenarios to suit your needs.

1. [Parking Your Users' Idle Funds in Curvance](/app/developer-docs/quick-start-guides/integration-cookbook#parking-your-users-idle-funds-in-curvance)
2. [Include a Curvance Market in Your Vault](/app/developer-docs/quick-start-guides/integration-cookbook#include-a-curvance-market-in-your-vault)
3. More coming soon

{% hint style="danger" %}
To minimize the risk of bad debt and potential loss of funds, deposit user funds only into conservative isolated markets with low-risk profiles. If the goal is lender yield, also confirm `await marketManager.debtCaps(cToken) > 0n`; `0n` means the asset can be deposited but cannot be borrowed in that market.
{% endhint %}

***

### Parking Your Users' Idle Funds in Curvance

**Objectives:**

* Safely park user idle funds in Curvance markets.

#### One time setup (per cToken market)

The user calls [`cUSDC.setDelegateApproval(<yourPlatformAddress>, true)`](/app/developer-docs/plugin-and-delegation-system/delegable-actions#setdelegateapproval), delegating your platform's address as the delegate.

**From your platform**, call `approve` on the underlying asset (e.g., USDC) so the cToken can pull funds during `deposit`.&#x20;

{% hint style="info" %}
*Tip: approve a large amount once to avoid a per-deposit approval transaction.*
{% endhint %}

{% hint style="info" %}
Note: Users can mass-revoke all delegates anytime by calling `incrementApprovalIndex()` on the Central Registry.
{% endhint %}

#### **Depositing idle funds**

**From your platform:** source USDC (e.g., transferred to you by the user) and call [`cUSDC.deposit(assets, <userAddress>)`](/app/developer-docs/lending-protocol/ctoken#deposit).

{% hint style="info" %}
Note: deposits pull USDC from the caller (msg.sender), not the `receiver`.
{% endhint %}

#### Retrieving funds on demand

Call [`cUSDC.redeemFor(shares, <yourPlatformAddress>, <userAddress>)`](/app/developer-docs/lending-protocol/ctoken#redeemfor) to redeem the user's cUSDC shares and receive USDC.

{% hint style="info" %}
`redeemFor` uses the delegation system, so **no ERC20 share approval is required**; your platform just needs to be an approved delegate of the user.
{% endhint %}

{% hint style="info" %}
**Note:** there is no `withdrawFor`; withdrawals on behalf of a user must go through `redeemFor` (in shares). If the user wants to pull a specific amount of underlying, compute shares with `previewWithdraw(assets)` first, then call `redeemFor(shares, platform, user)`.
{% endhint %}

### Collateralizing on Behalf of Users

*If your integration wants user funds held **as collateral** (not just earning yield), plain* `deposit` *won't credit them as collateral.*

* The user must first delegate your platform via `setDelegateApproval`.
* Your platform then calls `cToken.depositAsCollateralFor(assets, <userAddress>)`; this deposits *and* posts the shares as collateral in one call.
* Alternative: call `postCollateralFor(shares, <userAddress>)` to post existing shares as collateral.
* Plain `depositAsCollateral` is restricted to `msg.sender == receiver` or the PositionManager, so non-self collateralization requires the `*For` variants + delegation.

{% hint style="info" %}
**Note:** posting collateral or borrowing starts an account-level 20-minute cooldown. During the cooldown, deposits still work, but repayment and cToken transfer, redeem, and withdraw paths that call the MarketManager transfer check revert. If your UX offers instant exit or repayment, gate it on the cooldown expiry or surface a wait time to the user.
{% endhint %}

***

### Include a Curvance Market in Your Vault

**Objectives:**

* Mint cTokens to your vault.
* Redeem cTokens to rebalance/withdraw

#### Mint cTokens when users mint vault shares

1. Approve cToken to spend the underlying ERC20 from your vault.&#x20;
2. Call [`cToken.deposit()`](/app/developer-docs/lending-protocol/ctoken#deposit) from your contract, with its address as the recipient.

#### Redeem cTokens to rebalance (if applicable) / or withdraw user funds

Call [`cToken.redeem()`](/app/developer-docs/lending-protocol/ctoken#redeem) if redeeming in shares, or [`cToken.withdraw()`](/app/developer-docs/lending-protocol/ctoken#withdraw) if redeeming in underlying.

{% hint style="info" %}
No ERC20 share approval is required **because your vault is both the caller and the owner of the shares** (standard ERC4626 behavior: allowance is only checked when `msg.sender != owner`).
{% endhint %}

***

### Stale vs Fresh Reads

Curvance cTokens accrue interest lazily: the exchange rate updates only when someone calls a state-changing function (`deposit`, `withdraw`, `redeem`, `borrow`, `repay`, liquidation). Between those on-chain touches, the real rate drifts upward (for lenders) as time passes, but **`view` functions cannot accrue state and therefore return slightly stale values**.

This matters for any integration that displays balances, computes liquidation margins, or tries to hit exact target amounts.

| Surface                                   | Acceptable read                                  | Why                                                                                       |
| ----------------------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------- |
| UX hint, "you'd receive \~X"              | `previewRedeem`, `previewWithdraw` (stale)       | Drift is sub-bp; preview rounding matches the eventual tx                                 |
| Health factor, liquidation margin         | `getSnapshotUpdated.staticCall(account)` (fresh) | Stale values can mask imminent liquidation                                                |
| "Redeem all" share count                  | `balanceOf(user)` (stale)                        | The on-chain `redeem` accrues before payout, so user receives the freshly-earned interest |
| Backend accounting, "send exactly N USDC" | `exchangeRateUpdated.staticCall()` (fresh)       | Wei-level accuracy required                                                               |
| Collateral cap UI                         | `marketCollateralPosted` (stale)                 | Share-count invariant, not rate-sensitive                                                 |

#### What is and isn't stale

<table><thead><tr><th>Function</th><th width="239">Accrues first?</th></tr></thead><tbody><tr><td><code>convertToAssets</code> / <code>convertToShares</code></td><td>No, stale</td></tr><tr><td><code>previewDeposit</code> / <code>previewMint</code> / <code>previewWithdraw</code> / <code>previewRedeem</code></td><td>No, stale</td></tr><tr><td><code>totalAssets</code> / <code>exchangeRate</code> / <code>maxWithdraw</code> / <code>maxRedeem</code></td><td>No, stale</td></tr><tr><td><code>getSnapshot(account)</code></td><td>No, stale</td></tr><tr><td><code>exchangeRateUpdated()</code></td><td>Yes, fresh</td></tr><tr><td><code>getSnapshotUpdated(account)</code></td><td>Yes, fresh</td></tr><tr><td>Actual <code>deposit</code> / <code>withdraw</code> / <code>redeem</code> / <code>borrow</code> / <code>repay</code></td><td>Yes, fresh (accrues before computing)</td></tr></tbody></table>

#### Practical implications

1. **Display staleness is in the user's favor on redemption.** A stale `previewRedeem` under-reports what the user will actually receive. The real `redeem` accrues first, using the fresher (higher) rate, so the user ends up with slightly more than previewed.
2. **Watch direction when converting from a target asset amount.** If a user enters "1000 USDC" and you compute shares via `previewWithdraw(1000)` then call `redeem(shares, ...)` or `redeemFor(shares, ...)`, the user can receive slightly more than 1000 USDC because the on-chain rate is fresher. If the caller is also the share owner, or has ERC20 share allowance from the owner, use `withdraw(1000, receiver, owner)` to target an exact asset amount. If your platform only has Curvance delegation, there is no cToken `withdrawFor`; use `previewWithdraw(assets)` plus `redeemFor(shares, receiver, owner)` and treat the output as at least the target, subject to state changes, liquidity, pauses, and account checks.
3. "Redeem all" is safe from a staleness perspective. Pass `balanceOf(userAddress)` into `redeem(shares, ...)` and the on-chain accrual pulls freshly-earned interest into the payout. The transaction can still revert due to cooldown, redemption pause, insufficient held assets, or collateral and health checks.
4. **Liquidation margin and health factor must use fresh reads.** If your UX shows "you can borrow X more" or "liquidation at Y", computing that from stale `getSnapshot` can display a user as healthier than they actually are, masking imminent liquidation. Always use `getSnapshotUpdated` via `staticCall` for these surfaces.
5. **Collateral cap display can safely use stale reads.** `marketCollateralPosted` is a share-count invariant, not a rate-sensitive value; staleness doesn't apply.

#### Fresh reads via `staticCall` (no gas, no state change)

`exchangeRateUpdated()` and `getSnapshotUpdated(account)` are **not** `view`; they call `_accrueIfNeeded()` before reading. A normal call would cost gas, but `staticCall` simulates the transaction and returns the accrued result without committing any state. This is the idiomatic pattern for "fresh reads":

```js
// Freshly-accrued exchange rate (WAD-scaled, shares-to-assets)
const freshRate = await cUSDC.exchangeRateUpdated.staticCall();

// Freshly-accrued AccountSnapshot: 
// { asset, underlying, decimals, isCollateral, collateralPosted, debtBalance }
const snap = await cUSDC.getSnapshotUpdated.staticCall(userAddress);
```

**When to use which:**

* **`convertTo*` / `preview*`**: cheap, stale. Fine for UX hints, rounding behavior that matches the actual tx, and sanity-checks that a value is within range. Use for low-stakes surfaces that update frequently anyway.
* **`exchangeRateUpdated.staticCall()` / `getSnapshotUpdated.staticCall()`**: fresh, slightly more RPC overhead than a pure view call but no gas. Use whenever wei-accuracy matters: health factors, liquidation-margin displays, "you will receive at least X" guarantees, and any backend accounting.
* **Actual `withdraw` / `redeem` / `borrow` / `repay`**: always use the fresh rate on-chain; you don't need to pre-accrue, and there's no way for your tx to use a stale rate.

**Forcing accrual on-chain.** If your contract needs `exchangeRate()`, `getSnapshot()`, or `convertTo*` to be accrued before a same-tx downstream read, call `cToken.accrueIfNeeded()` first. It runs `_accrueIfNeeded()` and writes the fresh state to storage so subsequent stale-view reads in the same tx return current values.

#### Stale estimates (illustrative, fine for UX hints)

```js
const userAddress = await signer.getAddress();

// cUSDC is a per-market cToken address. Source the correct address
// from the deployment registry for the chain and market you are integrating.
const cUSDC = new ethers.Contract(
  ADDRESSES.USDC_CTOKEN,
  CTOKEN_ABI,
  signer
);

// Amount of USDC for a given share count (stale)
const shares = await cUSDC.balanceOf(userAddress);
const assetsOut = await cUSDC.convertToAssets(shares);

// Shares for a given USDC amount (stale)
const assetsIn      = ethers.parseUnits("1000", 6);
const sharesApprox  = await cUSDC.convertToShares(assetsIn);  // rounds DOWN
const sharesExact   = await cUSDC.previewWithdraw(assetsIn);  // rounds UP, matches the actual withdraw call
```

***

#### Practical Tips

* Use previews (`previewDeposit()`, `previewWithdraw()`, `previewMint()`, `previewRedeem()`) client-side for UX estimates. `maxDeposit()` and `maxMint()` return `0` only when the cToken is unlisted or specifically mint-paused; they do not surface deposit caps or other pause flags. For full pause coverage, call `marketManager.actionsPaused(cToken)` and inspect all three flags `(mintPaused, collateralizationPaused, borrowingPaused)`.
* Pauses/caps may limit deposits/redemptions; handle errors gracefully.
* **Reentrancy:** all deposit/redeem/collateral entrypoints are `nonReentrant` (transient-storage lock). If your integration calls into cTokens from a callback, it will revert.
* **Security:** delegation lets your platform act for users; encourage users to delegate only trusted contracts and remind them they can revoke instantly via approval index.
* **Self-delegation is blocked:** `setDelegateApproval(msg.sender, ...)` reverts; users can't set themselves as their own delegate, so your platform must be a distinct address.


# Loans & Collateral

This guide demonstrates how to integrate with Curvance Protocol using JavaScript and **ethers.js v6**, with examples using **ezETH** as collateral and **WETH** as the borrowable asset. The same patterns apply to any Curvance market on any deployment chain (Monad, Ethereum mainnet); replace addresses from the deployment registry for the chain you're on.

{% hint style="info" %}
The MarketManager coordinates all cTokens **inside a single isolated market**. For a deep dive, see [Market Manager](/app/developer-docs/lending-protocol/market-manager).
{% endhint %}

### Setting Up Your Integration

Install ethers.js. These examples use ethers 6.x:

```bash
npm install ethers@6
```

Define your contract addresses and ABIs.

```js
const ADDRESSES = {
  EZETH:          "0x...", // underlying collateral token (ezETH)
  WETH:           "0x...", // underlying debt token (WETH)
  EZETH_CTOKEN:   "0x...", // Curvance collateral cToken wrapping ezETH
  WETH_CTOKEN:    "0x...", // Curvance borrowable cToken wrapping WETH
  MARKET_MANAGER: "0x...", // MarketManager for the ezETH | WETH market
};
```

Source these from the deployment registry for the chain you're targeting; never mix placeholders and live addresses in the same code block. If your flow borrows WETH, confirm `await marketManager.debtCaps(ADDRESSES.WETH_CTOKEN) > 0n` in that deployment.

### Important Considerations Before Depositing

**Protocol Status, verify the action you're about to take is not paused.**

Call `actionsPaused(cToken)` on the MarketManager. It returns a tuple of three booleans: `(mintPaused, collateralizationPaused, borrowingPaused)`. There is no standalone `mintPaused()` function.

```js
const marketManager = new ethers.Contract(
  ADDRESSES.MARKET_MANAGER,
  MARKET_MANAGER_ABI,
  provider
);

const postingAsCollateral = true;

const [mintPaused, collateralizationPaused] =
  await marketManager.actionsPaused(ADDRESSES.EZETH_CTOKEN);

if (mintPaused) throw new Error("Minting is paused");
if (postingAsCollateral && collateralizationPaused) {
  throw new Error("Collateralization is paused");
}
```

**Collateral Caps, if depositing as collateral.**

`collateralCaps(cToken)` lives on the MarketManager and returns a `uint256`: the maximum shares that can be posted as collateral.

`marketCollateralPosted()` lives on the **cToken** (not the MarketManager) and takes **no arguments** (it's a public state variable). It returns the current shares posted as collateral.

```js
const cToken = new ethers.Contract(
  ADDRESSES.EZETH_CTOKEN,
  CTOKEN_ABI,
  provider
);

const depositAmount = ethers.parseEther("1");
const expectedShares = await cToken.previewDeposit(depositAmount);

const cap = await marketManager.collateralCaps(ADDRESSES.EZETH_CTOKEN);
const posted = await cToken.marketCollateralPosted();

if (cap === 0n) throw new Error("Collateralization disabled for this cToken");
if (posted + expectedShares > cap) throw new Error("Collateral cap exceeded");
```

**Note:** a `collateralCaps` of `0` means collateralization is **disabled entirely** for that cToken, not "unlimited." Don't treat zero as "no cap." (Source: `MarketManagerIsolated.sol:376` reverts on `newNetCollateral > collateralCaps[collateralToken]` with a non-zero collateral amount.)

**Minimum Hold Period.**&#x41;fter an account posts collateral or borrows, a 20-minute account cooldown starts. During this window the affected account cannot:

* repay a loan,
* transfer collateralized shares,
* **redeem collateralized shares** (i.e., withdraw the underlying out of a position you've posted as collateral).
* remove collateral through paths that call the MarketManager redeem check.

Plain deposits are unaffected; the gate trips on actions that reduce a collateralized position. Enforced by `MIN_HOLD_PERIOD = 20 minutes` in `MarketManagerIsolated.sol:115`; checked by `_checkHoldPeriod` (`:1659`) reached from both `canRepayWithReview` (`:496`) and `_checkTransfersAllowed` (`:1693`). Mitigates flash-loan and multi-block oracle manipulation.

**Non-Rebasing.** cToken share balances are fixed; yield accrues by increasing the exchange rate (shares to underlying), not by minting additional shares.

### Best Practices for Production Implementations

* **Error Handling:** wrap all transactions with try/catch and surface revert reasons to users.
* **Gas Estimation:** call `contract.foo.estimateGas(args)` before sending to detect likely reverts and set safe limits.
* **Approval Checking:** read `allowance()` before submitting an `approve` tx; skip if sufficient.
* **Transaction Monitoring:** track tx status via receipts and confirmations; always `await tx.wait()` between dependent transactions (the approve-then-deposit race is real).
* **Read-Only Checks:** use `canMint(cToken)` on the MarketManager as the deposit precheck. It returns no value; success means minting is allowed, and failure reverts. If your ABI marks it as nonpayable, call `await marketManager.canMint.staticCall(cToken)` to force an `eth_call`. `canBorrow` and `canRedeemWithCollateralRemoval` are not clean UX prechecks; they mutate state or are cToken-only paths and revert on failure. For UX estimates, use the ERC4626 preview functions: `previewDeposit`, `previewMint`, `previewRedeem`, `previewWithdraw`. These preview functions are stale views; for wei-accurate fresh reads, see [Integration Cookbook: Stale vs Fresh Reads](/app/developer-docs/quick-start-guides/integration-cookbook).
* **Reentrancy:** every cToken deposit / redeem / borrow / repay / collateral entrypoint is `nonReentrant` (transient-storage lock). Calling back into Curvance from a callback reverts. Structure flows so Curvance calls are not nested.
* **Delegation model:** if your platform acts on behalf of users, the user must call `setDelegateApproval(yourAddress, true)` on the relevant cToken first. Use the `*For` variants: `redeemFor`, `depositAsCollateralFor`, `postCollateralFor`, `removeCollateralFor`. Plain `redeem` / `withdraw` consume ERC20 share allowance when `msg.sender != owner`. There is **no cToken `withdrawFor`**: use `redeemFor` with shares computed from `previewWithdraw(assets)` if the caller wants a specific underlying amount. Self-delegation is blocked. Users can mass-revoke delegates by calling `centralRegistry.incrementApprovalIndex()`.

By following this guide, you'll be able to integrate with Curvance's deposit functionality for both yield-optimized collateral positions and lending positions using JavaScript and ethers.js v6.

***


# Lend Assets

### Depositing USDC for Lending

To deposit USDC into cUSDC directly, use the `deposit()` function present in all cToken contracts (defined on `BaseCToken.sol:534`). It takes two arguments: the amount of underlying assets and the recipient of the minted cToken shares. It returns the number of shares minted.

{% hint style="info" %}
For a detailed explanation of BorrowableCTokens, check out the [BorrowableCTokens](/app/developer-docs/lending-protocol/borrowablectoken) page.
{% endhint %}

Below is a full implementation:

```js
// Get the cUSDC contract with signer.
// USDC_CTOKEN is per isolated market, not a single global address; pull the
// address for the specific market you are depositing into from the deployment registry.
const cUSDC = new ethers.Contract(ADDRESSES.USDC_CTOKEN, CTOKEN_ABI, signer);
// Get the underlying USDC contract with signer.
const usdc = new ethers.Contract(ADDRESSES.USDC, ERC20_ABI, signer);

// Format amount (USDC has 6 decimals)
const depositAmount = ethers.parseUnits("1000", 6); // 1000 USDC
const userAddress = await signer.getAddress();

const maxAssets = await cUSDC.maxDeposit(userAddress);
if (maxAssets === 0n || depositAmount > maxAssets) {
  throw new Error("Deposit is not currently available for this cToken");
}

// Preview the shares expected from this deposit.
// This is an estimate from the last-accrued exchange rate, not a guarantee.
const expectedShares = await cUSDC.previewDeposit(depositAmount);
if (expectedShares === 0n) throw new Error("Deposit amount would mint zero shares");

// Pre-flight: skip the approve tx if allowance is already sufficient
const current = await usdc.allowance(userAddress, ADDRESSES.USDC_CTOKEN);
if (current < depositAmount) {
  const approveTx = await usdc.approve(ADDRESSES.USDC_CTOKEN, depositAmount);
  await approveTx.wait();
}

// Deposit USDC for lending
const depositTx = await cUSDC.deposit(depositAmount, userAddress);
await depositTx.wait();
```

#### Pre-flight checks before depositing

* Confirm `(mintPaused, , ) = marketManager.actionsPaused(cUSDC)` returns `false` for the mint flag, or simply check `await cUSDC.maxDeposit(userAddress) > 0n`. `maxDeposit` returns `0` if mint is paused or the cToken is not listed in this market manager (`BaseCToken.sol:500`), so `> 0n` is a slightly stronger precheck. See the Loans & Collateral quick start for the full pause-API discussion.
* `await cUSDC.previewDeposit(depositAmount)` before submitting to estimate how many shares you will receive and catch dust deposits that would mint `0` shares. `previewDeposit` is a `view` that reads last-accrued state (`BaseCToken.sol:695`); the actual `deposit()` path accrues first, then converts assets to shares (`BaseCToken.sol:746`). If interest accrued since the last on-chain touch, the stale preview can overestimate shares. For fresh exchange-rate reads, see Stale vs Fresh Reads.

#### What happens when you deposit

When you deposit USDC into cUSDC:

* Your USDC tokens are transferred to the cToken contract.
* You receive cUSDC tokens representing your lending position.
* Your USDC becomes available for borrowing by users **inside the same isolated market**, subject to current liquidity, borrow pause status, collateral requirements, and `marketManager.debtCaps(ADDRESSES.USDC_CTOKEN) > 0`. Curvance markets are isolated by MarketManager, not one global pool.
* As borrowers pay interest, the exchange rate between cUSDC and USDC increases (cToken balances are non-rebasing; yield shows up as a higher exchange rate, not as new shares).
* When you redeem later, your shares convert back to USDC at the current exchange rate, subject to available liquidity and redemption checks. If the market has accrued interest, that exchange rate reflects lender yield.

### Lending vs. collateralized deposits

Depositing via plain `deposit()` does not post your shares as collateral; your deposit is pure lending liquidity. If you want the same deposit to also count as collateral (so you can borrow against it), call `depositAsCollateral(assets, receiver)` instead.

**Restriction:** unlike plain `deposit()`, `depositAsCollateral` requires `receiver == msg.sender` (or that `msg.sender` is a registered PositionManager). Enforced at `BaseCToken.sol:237`:

```solidity
if (msg.sender != receiver && !marketManager.isPositionManager(msg.sender)) {
    _revert(_UNAUTHORIZED_SELECTOR);
}
```

To collateralize on someone else's behalf, that user must first call `setDelegateApproval(yourAddress, true)` on the cToken, after which you call `depositAsCollateralFor(assets, receiver)`. See [Collateralize](/app/developer-docs/quick-start-guides/loans-and-collateral/collateralize) for the full flow.

***


# Collateralize

### Depositing ezETH as Collateral

When depositing into a cToken as collateral, your deposit also serves as lending liquidity if the cToken itself is borrowable (`TokenConfig.debtCap > 0`).

* **Collateral-only assets** (for example, ezETH and deployment-specific LSTs or yield-bearing wrappers such as shMON, aprMON, or sAUSD): `debtCap == 0`, so users cannot borrow that asset from the cToken. The deposit functions as collateral; it does not earn borrower-paid lending yield from that cToken.
* **Borrowable assets used as collateral** (e.g., WETH or USDC deposited as collateral): `debtCap > 0`, so the deposit both backs your debt position and earns interest from other borrowers simultaneously.

To verify programmatically: `await marketManager.debtCaps(cToken)`. Zero means the asset is collateral-only.

### Pre-flight checks

Before depositing, confirm the market accepts new collateral:

```js
const marketManager = new ethers.Contract(
  ADDRESSES.MARKET_MANAGER,
  MARKET_MANAGER_ABI,
  provider
);

const cToken = new ethers.Contract(ADDRESSES.EZETH_CTOKEN, CTOKEN_ABI, signer);
const ezETH = new ethers.Contract(ADDRESSES.EZETH, ERC20_ABI, signer);
const userAddress = await signer.getAddress();
const depositAmount = ethers.parseEther("10"); // 10 ezETH

// Pause check (returns [mintPaused, collateralizationPaused, borrowingPaused])
const [mintPaused, collateralizationPaused] =
  await marketManager.actionsPaused(ADDRESSES.EZETH_CTOKEN);
if (mintPaused) throw new Error("Minting is paused");
if (collateralizationPaused) throw new Error("Collateralization is paused");

// Collateral cap check. Cap and posted values are cToken shares.
const expectedShares = await cToken.previewDeposit(depositAmount);
const cap = await marketManager.collateralCaps(ADDRESSES.EZETH_CTOKEN);
const posted = await cToken.marketCollateralPosted();
if (cap === 0n) throw new Error("Collateralization disabled for this cToken");
if (posted + expectedShares > cap) throw new Error("Collateral cap exceeded");
```

{% hint style="info" %}
`previewDeposit` is a view estimate. The actual transaction accrues state before minting shares, and state can move before the transaction mines. For stale-vs-fresh read details, see [Integration Cookbook: Stale vs Fresh Reads](/app/developer-docs/quick-start-guides/integration-cookbook).
{% endhint %}

#### Balance check

Confirm the user has enough underlying before approving and depositing:

```js
const ezETH = new ethers.Contract(ADDRESSES.EZETH, ERC20_ABI, provider);
const userAddress = await signer.getAddress();

const balance = await ezETH.balanceOf(userAddress);
const depositAmount = ethers.parseEther("10"); // 10 ezETH

if (balance < depositAmount) {
  throw new Error("Insufficient ezETH balance");
}
```

### Approve and deposit

Then approve and deposit. Curvance exposes two options:

* `deposit(assets, receiver)` deposits tokens without posting as collateral (lending-only flow).
* `depositAsCollateral(assets, receiver)` deposits and posts the resulting shares as collateral in a single transaction.

```js
// Skip approval if allowance already sufficient
const currentAllowance = await ezETH.allowance(userAddress, ADDRESSES.EZETH_CTOKEN);
if (currentAllowance < depositAmount) {
  const approveTx = await ezETH.approve(ADDRESSES.EZETH_CTOKEN, depositAmount);
  await approveTx.wait();
}

// Deposit: as collateral, or lending-only
let depositTx;
if (asCollateral) {
  depositTx = await cToken.depositAsCollateral(depositAmount, userAddress);
} else {
  depositTx = await cToken.deposit(depositAmount, userAddress);
}
await depositTx.wait();
```

**Caller restriction for `depositAsCollateral`:** the function requires `msg.sender == receiver` unless `msg.sender` is a registered PositionManager. In practice, a user wallet collateralizes for itself. If your integration needs to deposit collateral on behalf of a user, use the delegated flow below. Enforced at `BaseCToken.sol:237`.

### Depositing on behalf of another address

If your app deposits collateral on behalf of a user, use `depositAsCollateralFor(assets, receiver)`:

```js
// The user must have pre-delegated your platform via:
//   ezETH_cToken.setDelegateApproval(<yourPlatformAddress>, true)
const depositTx = await cToken.depositAsCollateralFor(depositAmount, targetUser);
await depositTx.wait();
```

**Requirements:**

* `targetUser` must have called `setDelegateApproval(yourAddress, true)` on the cToken first.
* The caller, not `targetUser`, supplies the underlying assets. `depositAsCollateralFor` checks delegation for `receiver`, then `_processDeposit` pulls the underlying from `msg.sender`.
* `msg.sender` cannot be `targetUser`. Self-delegation is blocked at the registration step (`PluginDelegable.sol:67`: `if (delegate == msg.sender) revert PluginDelegable_InvalidParameter()`), so the self-self approval slot is always `false` and `_checkDelegate` reverts with `PluginDelegable__Unauthorized()`. If you're acting on your own behalf, use `depositAsCollateral`.

See the Plugin Integration guide for the full delegation model, including mass-revoke via `centralRegistry.incrementApprovalIndex()`.

### What happens when you deposit as collateral

* Your ezETH is transferred to the cToken contract.
* You receive cezETH shares representing your position.
* The shares are marked as collateral (`collateralPosted[account]` increments on the cToken; the mapping is public at `BaseCToken.sol:116`, queryable as `cToken.collateralPosted(account)`), enabling borrowing in the same isolated market against them.
* A 20-minute hold period starts on your account (`MIN_HOLD_PERIOD` in `MarketManagerIsolated.sol:115`; revert error `MarketManager__MinimumHoldPeriod` at `:195`). During this window, repayment for that account, redemption / withdrawal from that account, and share transfers from that account all revert. The cooldown is set by collateral postings (`:382`) and new borrows (`:463`, `:476`); plain `deposit()` calls do **not** trigger or reset it. Plain deposits still work during an active cooldown but leave the timer untouched. New collateral postings and new borrows also work if otherwise valid, and each one resets the 20-minute window from the current block. This mitigates flash-loan and multi-block price-manipulation attacks.
* If ezETH is collateral-only (`debtCap == 0`), your position earns no borrower-paid lending yield from that cToken; it purely backs borrows. If the collateralized asset is borrowable, it can earn interest from borrowers in addition to backing your debt position.

***


# Withdraw

### Querying Redeem Amounts

This page uses cUSDC in a \`WETH | USDC\` or \`WBTC | USDC\` isolated market as the example. Use the cUSDC address for the specific market from the deployment registry for the chain you're on.

To display the amount of underlying tokens a user would receive for redeeming a given number of shares, use `convertToAssets(shares)`:

```js
const cUSDC     = new ethers.Contract(ADDRESSES.CUSDC, CTOKEN_ABI, provider);
const shares    = await cUSDC.balanceOf(userAddress);
const assetsOut = await cUSDC.convertToAssets(shares);
```

Conversely, if you want to let the user choose how much underlying to withdraw and need to know how many shares will be burned, use `previewWithdraw(assets)`. `convertToShares(assets)` is also available, but it rounds down and can under-quote the shares actually burned by `withdraw()`.

```js
const userInputAmount = "1000"; // 1000 USDC entered by the user
const assetsIn        = ethers.parseUnits(userInputAmount, 6);

// previewWithdraw rounds UP and matches the shares the withdraw() call will actually burn
const sharesExact     = await cUSDC.previewWithdraw(assetsIn);

// convertToShares rounds DOWN: useful as a generic rate quote, NOT for shares-to-burn UX
const sharesApprox    = await cUSDC.convertToShares(assetsIn);
```

{% hint style="warning" %}
`convertTo*` and `preview*` are view functions. They read the last-accrued state, while write calls such as `withdraw` and `redeem` accrue first. For wei-accurate exchange-rate reads, use the `staticCall`-on-`exchangeRateUpdated()` pattern documented in Integration [Cookbook: Stale vs Fresh Reads](/app/developer-docs/quick-start-guides/integration-cookbook#stale-vs-fresh-reads).
{% endhint %}

**When to use which:**

* `convertToShares` / `convertToAssets` are generic rate conversions, both round DOWN.
* `previewWithdraw` rounds UP and matches `withdraw()`.
* `previewRedeem` rounds DOWN and matches `redeem()`.
* For a target-asset-amount UX, prefer `withdraw(assets, ...)` directly over computing shares and calling `redeem`; `withdraw` accrues on-chain and hits the exact target.

***

### Withdraw from cTokens (cUSDC example)

To withdraw USDC from cUSDC directly, use `withdraw(assets, receiver, owner)` if the user chose an amount in USDC, or `redeem(shares, receiver, owner)` if the user chose an amount in shares.

`owner` is the account whose shares will be burned. When the user is withdrawing their own position, `owner === msg.sender` and no ERC20 share allowance is required. If `msg.sender !== owner`, plain `withdraw` / `redeem` spend ERC20 allowance over `owner`'s cToken shares. To use Curvance delegation instead of ERC20 allowance, use `redeemFor` as covered below.

**Withdrawing/redeeming does not force collateral removal.** These functions burn uncollateralized shares first. If the user's uncollateralized balance is insufficient, the protocol removes only the collateralized shares needed to complete the redemption, and only if the position remains healthy afterward. To intentionally redeem posted collateral, use `withdrawCollateral`, `redeemCollateral`, or `redeemCollateralFor` covered below.

#### Example: `withdraw(assets, receiver, owner)`

User enters an amount in USDC and wants exactly that much USDC out.

```js
const cUSDC       = new ethers.Contract(ADDRESSES.CUSDC, CTOKEN_ABI, signer);
const userAddress = await signer.getAddress();

const usdcOut     = ethers.parseUnits(userInputAmount, 6); // user types "1000"

// Optional: preview the exact shares that will be burned
const sharesBurned = await cUSDC.previewWithdraw(usdcOut);

// Withdraw USDC; receiver gets the USDC, owner has shares burned
const tx = await cUSDC.withdraw(usdcOut, userAddress, userAddress);
await tx.wait();
```

#### Example: `redeem(shares, receiver, owner)`

Use `redeem` when the user has expressed their intent in shares, most commonly "redeem all." 🔧

```js
const cUSDC       = new ethers.Contract(ADDRESSES.CUSDC, CTOKEN_ABI, signer);
const userAddress = await signer.getAddress();

// Redeem all: burn the user's entire share balance
const allShares = await cUSDC.balanceOf(userAddress);
const tx        = await cUSDC.redeem(allShares, userAddress, userAddress);
await tx.wait();
```

{% hint style="warning" %}
**"Redeem all" caveat:** `balanceOf(owner)` is the raw share balance, not a guarantee that every share is currently redeemable. If shares are posted as collateral, the full redeem can revert on health checks. If the market is inside the hold period, redemption reverts. If idle liquidity is insufficient, a borrowable cToken redemption can also revert.
{% endhint %}

If your UX expresses the amount in USDC but you still prefer to call `redeem`, compute shares via `previewWithdraw(assets)` first. This gives you an asset-aware ceiling: using the same exchange rate, `redeem(thoseShares)` returns at least the requested asset amount after rounding. State can still move before the transaction finalizes.

#### Using `redeemFor` (for platforms withdrawing on behalf of users)

If your app withdraws from a user's position (e.g., a managed-vault integration), use `redeemFor(shares, receiver, owner)`. The signature matches `redeem` but the authorization check switches from ERC20 allowance to the delegation system.

```js
// The user must have pre-delegated your platform:
//   cUSDC.setDelegateApproval(<yourPlatformAddress>, true)
const tx = await cUSDC.redeemFor(sharesToRedeem, yourPlatformAddress, userAddress);
await tx.wait();
```

**Key points:**

* No ERC20 share approval needed; delegation replaces allowance.
* `receiver` (the USDC destination) can be your platform or any address; `owner` must be the user.
* **Note:** there is no `withdrawFor`. If your integration wants to pull a specific USDC amount on behalf of a user, compute shares with `previewWithdraw(assets)` first, then call `redeemFor`.

See [Plugin Integration](/app/developer-docs/quick-start-guides/plugin-integration) for the full delegation model and mass-revoke via `centralRegistry.incrementApprovalIndex()`.

***

### Withdrawing Collateral

When redeeming shares that are currently posted as collateral, choose the function that matches the user's intent.

```js
// User withdraws an exact amount of USDC from their own posted collateral.
// This path uses ERC20 share allowance if msg.sender != owner.
const tx1 = await cUSDC.withdrawCollateral(usdcOut, userAddress, userAddress);
await tx1.wait();

// User redeems an exact number of their own posted collateral shares.
// No ERC20 share approval is needed when msg.sender == owner.
const tx2 = await cUSDC.redeemCollateral(shares, userAddress, userAddress);
await tx2.wait();

// Platform redeems collateral shares on behalf of a delegated user.
const tx3 = await cUSDC.redeemCollateralFor(shares, yourPlatformAddress, userAddress);
await tx3.wait();
```

These functions force the redeemed shares to come from `owner`'s `collateralPosted`. If `shares` is greater than the owner's posted collateral, the MarketManager reverts with `MarketManager__InsufficientCollateral`. Plain `redeem` / `withdraw` behave differently: they consume idle shares first and only remove posted collateral when required to complete the redemption.

There is no cToken `withdrawCollateralFor`. If a delegated integration wants a specific underlying amount from posted collateral, compute shares with `previewWithdraw(assets)` first, then call `redeemCollateralFor(shares, receiver, owner)`.

{% hint style="warning" %}
**Caution: collateral removal affects your loan-to-value ratio.** Removing collateral increases your loan-to-value (LTV) and can increase liquidation risk. The protocol will reject the redemption if it would push the position below its collateralization requirement, but the margin of safety is up to the integrator to surface to the user.
{% endhint %}

{% hint style="info" %}
**Hold period gotcha:** posting collateral or borrowing starts a 20-minute `MIN_HOLD_PERIOD`. During this window, repayment, cToken transfers in the same isolated market, and redemption / withdrawal paths that call the MarketManager redeem check revert with `MarketManager__MinimumHoldPeriod`. The redeem path enforces this through `canRedeemWithCollateralRemoval` → `_canRedeem` → `_checkTransfersAllowed` → `_checkHoldPeriod` (`MarketManagerIsolated.sol:406`, `:1303`, `:1688`, `:1654`). Plan redemption flows so the user is not inside a fresh cooldown window.
{% endhint %}

***

### Pre-flight checks for redemptions

Do not treat `maxRedeem(owner)` or `maxWithdraw(owner)` as final Curvance redemption prechecks. In the base ERC4626 implementation, `maxRedeem(owner)` returns `balanceOf(owner)` and `maxWithdraw(owner)` returns `convertToAssets(balanceOf(owner))`. They do not check the MarketManager hold period, market-wide redeem pause, collateral health, or available idle liquidity.

For a borrowable cToken such as cUSDC, use targeted checks before submitting:

```js
const marketManager = new ethers.Contract(
  ADDRESSES.MARKET_MANAGER,
  MARKET_MANAGER_ABI,
  provider
);

const cUSDC = new ethers.Contract(ADDRESSES.USDC_CTOKEN, CTOKEN_ABI, signer);
const owner = await signer.getAddress();
const MIN_HOLD_PERIOD = 20n * 60n;

// Market-wide redemption pause. Public getter returns 2 when paused.
const redeemPaused = await marketManager.redeemPaused();
if (redeemPaused === 2n) {
  throw new Error("Redemptions are paused for this market");
}

// Account cooldown. accountAssets(owner) returns the cooldown timestamp.
const latestBlock = await provider.getBlock("latest");
if (latestBlock === null) throw new Error("Could not load latest block");

const cooldownStarted = await marketManager.accountAssets(owner);
const cooldownEnds = cooldownStarted + MIN_HOLD_PERIOD;
if (cooldownEnds > BigInt(latestBlock.timestamp)) {
  throw new Error("Account is still inside the 20-minute hold period");
}

// Balance and available idle-liquidity checks.
const sharesToRedeem = await cUSDC.balanceOf(owner);
if (sharesToRedeem === 0n) throw new Error("No cUSDC shares to redeem");

const assetsOut = await cUSDC.previewRedeem(sharesToRedeem);
const idleAssets = await cUSDC.assetsHeld();
if (assetsOut > idleAssets) {
  throw new Error("Not enough idle USDC liquidity for this redemption");
}
```

If the redemption touches posted collateral, also run a collateral-health check before submitting. If your integration uses `ProtocolReader`, `maxRedemptionOf(account, cToken, bufferTime)` returns separate estimates for collateralized and uncollateralized shares, and `hypotheticalRedemptionOf(account, cToken, redemptionShares, bufferTime)` reports whether a proposed redemption would leave a liquidity deficit. These are UX helpers; the cToken transaction is still the source of truth.

These checks map to `redeemPaused` on the MarketManager, the public `accountAssets(account)` cooldown getter, and `assetsHeld()` on borrowable cTokens.

***


# Borrowing & Repayment

In Curvance's lending ecosystem, each isolated market has a set of cTokens, one per listed asset.

* **Lending-side accounting:** depositors receive cToken shares (ERC4626) that represent their claim on the underlying pool. Holding cUSDC shares means you deposited USDC and are earning interest as borrowers repay.
* **Borrowing-side accounting:** borrowers do not receive cToken shares when they borrow. Their debt is recorded separately on the same `BorrowableCToken` contract, using the internal `_debtOf` accounting, and grows as interest accrues. The borrower receives the underlying asset directly, while the cToken tracks what they owe.

For a deep dive, see the [BorrowableCTokens](/app/developer-docs/lending-protocol/borrowablectoken) page.

Use the subpages for transaction recipes:

* **Borrow:** opening a debt position against posted collateral.
* **Repaying Debt:** partial repayment, full repayment, and repayment on behalf of another account.
* **Flash Loans:** one-transaction liquidity from a borrowable cToken pool.

***

### Understanding `cUSDC`

`cUSDC` is the per-market `BorrowableCToken` wrapping USDC in a specific isolated market. The same concepts apply to the cTokens wrapping WETH, WBTC, AUSD, ezETH, and other listed assets. Source the real cToken address from the deployment registry for the chain you're on.

**Key characteristics:**

* **Underlying asset:** USDC, with 6 decimal places. (Underlying decimals vary by asset; always call `await underlying.decimals()` rather than hardcoding.)
* **Interest accrual:** borrow interest accrues lazily. Accruing paths include `deposit`, `withdraw`, `redeem`, `borrow`, `repay`, `liquidate`, `flashLoan`, `accrueIfNeeded`, and the updated-read helpers such as `exchangeRateUpdated`, `debtBalanceUpdated`, and `marketOutstandingDebtUpdated`. View functions can read last-accrued state, so use the `staticCall` pattern for fresh reads. See Integration Cookbook: Stale vs Fresh Reads.
* **Exchange rate:** the shares-to-assets rate increases as lenders earn interest. Lending positions are non-rebasing: share count stays fixed, value per share rises.
* **Dynamic IRM:** each borrowable cToken stores an `IRM()` address. The DynamicIRM adjustment interval is 10 minutes (`DynamicIRM.ADJUSTMENT_RATE()`), and rate updates happen lazily when accrual runs at or after an adjustment boundary. Higher utilization generally means a higher borrow APR.
* **20-minute hold period after posting collateral or borrowing:** posting collateral or borrowing starts an account-level cooldown inside that isolated market. During the next 20 minutes, repayment, redemption / withdrawal, and share transfers for that account revert with `MarketManager__MinimumHoldPeriod`. Plain deposits are not blocked and do not start or reset the cooldown. New collateral postings and new borrows are allowed if otherwise valid, and each one resets the cooldown.
* **Same cToken, not both roles for the same account:** a user cannot use the same cToken as posted collateral and debt at the same time. Borrowing from a cToken where the account has posted collateral reverts with `BorrowableCToken__CollateralPositionActive`. Posting collateral to a cToken where the account already has debt reverts with `BorrowableCToken__DebtPositionActive`. Typical flows borrow the other side of the isolated market, for example, deposit ezETH as collateral in an `ezETH | WETH` market and borrow WETH.

***

### Setting Up Your Development Environment

Before interacting with cUSDC, set up ethers v6 and load ABIs from the Curvance contracts repo or your generated ABI artifacts.

```js
const { ethers } = require('ethers');

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

// Placeholder addresses. Source the real values from the deployment registry
// for the chain you're targeting.
const cUSDCAddress      = '0x...';  // cUSDC contract for your target market
const USDCAddress       = '0x...';  // underlying USDC token on your target chain
const protocolReaderAddr = '0x...'; // Curvance ProtocolReader

// Initialize contract instances with the appropriate ABIs
const cUSDC          = new ethers.Contract(cUSDCAddress, BORROWABLE_CTOKEN_ABI, wallet);
const USDC           = new ethers.Contract(USDCAddress, ERC20_ABI, wallet);
const protocolReader = new ethers.Contract(protocolReaderAddr, PROTOCOL_READER_ABI, provider);
```

***

### Monitoring Your Debt Position

As interest accrues, monitor debt with the read that matches your UX. Use stale views for low-risk display copy, and fresh or projected reads for liquidation margin, close-out flows, and alerts.

#### Option A: `ProtocolReader.debtBalanceAtTimestamp`

Use this view for a current or future debt projection. If you pass a timestamp before the current block, the reader clamps it to the current block timestamp.

```js
async function checkProjectedDebtBalance() {
  const now = Math.floor(Date.now() / 1000);
  const debtBalance = await protocolReader.debtBalanceAtTimestamp(
    userAddress,
    cUSDCAddress,
    now
  );

  const USDCDecimals = await USDC.decimals();
  console.log(`Projected USDC debt: ${ethers.formatUnits(debtBalance, USDCDecimals)}`);
  return debtBalance;
}
```

`debtBalanceAtTimestamp` simulates debt at the supplied current or future timestamp without mutating chain state. It is useful when your UI wants to show what the account's debt will be after a short buffer.

#### Option B: `cUSDC.debtBalanceUpdated(account)` (fresh, via staticCall)

For a wei-accurate read at the current block, call `debtBalanceUpdated` with `staticCall`. This function accrues interest first before returning:

```js
const freshDebt = await cUSDC.debtBalanceUpdated.staticCall(wallet.address);
```

#### Option C: `cUSDC.debtBalance(account)` (view, cheap, stale)

The plain `view` version returns the last-accrued value, suitable for non-critical displays:

```js
const staleDebt = await cUSDC.debtBalance(wallet.address);
```

{% hint style="danger" %}
If your UX surfaces liquidation margin, health factor, or any "receive at least X" guarantee, use Option A or B. Stale reads can under-report pending debt and mask imminent liquidation. See the [Stale vs Fresh Reads](/app/developer-docs/quick-start-guides/integration-cookbook#stale-vs-fresh-reads) write-up for the full pattern.
{% endhint %}

Debt grows over time as interest accrues. If your debt grows faster than your collateral value, you risk liquidation. Maintain a collateralization buffer above the minimum and alert before the account approaches liquidation.

***

### Understanding Market Conditions

Borrow APR depends on utilization. The DynamicIRM computes utilization as:

```
utilization = outstandingDebt / (assetsHeld + outstandingDebt)
```

Higher utilization generally pushes borrow rates higher. Before borrowing, inspect the current market state.

```js
async function checkMarketInfo() {
  const USDCDecimals = await USDC.decimals();

  // Exchange-rate reads are WAD-scaled share-to-asset rates.
  const rateStale = await cUSDC.exchangeRate();
  const rateFresh = await cUSDC.exchangeRateUpdated.staticCall();

  console.log(`Stale exchange rate: ${ethers.formatUnits(rateStale, 18)}`);
  console.log(`Fresh exchange rate: ${ethers.formatUnits(rateFresh, 18)}`);

  // marketOutstandingDebt is a public uint240 storage variable. It reflects
  // last-accrued debt for this cToken's underlying asset.
  const totalBorrowsStale = await cUSDC.marketOutstandingDebt();
  const totalBorrowsFresh =
    await cUSDC.marketOutstandingDebtUpdated.staticCall();

  console.log(
    `Total USDC debt (stale): ${ethers.formatUnits(totalBorrowsStale, USDCDecimals)}`
  );
  console.log(
    `Total USDC debt (fresh): ${ethers.formatUnits(totalBorrowsFresh, USDCDecimals)}`
  );

  // A stale but internally consistent utilization display.
  const idleAssetsStale = await cUSDC.assetsHeld();
  const WAD = 10n ** 18n;
  const utilizationStale =
    totalBorrowsStale === 0n
      ? 0n
      : (totalBorrowsStale * WAD) / (idleAssetsStale + totalBorrowsStale);

  console.log(`Utilization (stale): ${ethers.formatUnits(utilizationStale, 18)}`);

  return { rateFresh, totalBorrowsFresh, utilizationStale };
}
```

Higher utilization generally leads to higher interest rates. Tracking these metrics can help you anticipate changes in borrowing costs and avoid taking on debt just before a rate spike.

`assetsHeld()` and `marketOutstandingDebt()` are view reads. They are useful for display and trend monitoring, but any transaction still rechecks the relevant conditions on-chain after accrual.

***

### Risk Considerations

When borrowing through `cUSDC`, be aware of:

* **Liquidation risk:** if collateral value falls or debt grows, the account can cross the market's liquidation threshold. Curvance uses soft and hard liquidation thresholds, plus per-token liquidation incentive and close-factor curves. The curve is based on how far the account is into liquidation.
* **Interest-rate volatility:** the DynamicIRM re-computes rates on a \~10-minute tick based on utilization. Your debt can grow faster than anticipated if utilization spikes.
* **Oracle risk:** live deployments use a single Chainlink-like adaptor per asset, with heartbeat checks and price guards for pegged or reference-priced assets. If a price-dependent action receives an invalid price signal, the affected borrow, redemption, or liquidation path reverts instead of proceeding with a bad price. Repayment is special: after the hold period, a full repay that leaves `newNetDebt == 0` skips the debt-asset oracle check, while a partial repay must still pass the residual loan-size price check.
* **Market risk:** the value of your collateral may decline relative to your borrowed asset, even without on-chain activity.
* **Hold-period interaction:** the 20-minute hold period after posting collateral or borrowing blocks repayment in that window. If your integration's liquidation-avoidance strategy depends on repaying on short notice, account for this lock.

To mitigate risk, keep a healthier collateralization buffer than the minimum, monitor positions with fresh or projected reads, and alert well before the account reaches liquidation.

***


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

***


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

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

```js
const MIN_HOLD_PERIOD_SECONDS = 20n * 60n;

async function assertRepayCooldownHasPassed(ownerAddress) {
  const latestBlock = await provider.getBlock("latest");
  if (latestBlock === null) throw new Error("Could not load latest block");

  // accountAssets(owner) returns the cooldown timestamp from the MarketManager.
  const cooldownStarted = await marketManager.accountAssets(ownerAddress);
  const cooldownEnds = cooldownStarted + MIN_HOLD_PERIOD_SECONDS;

  if (cooldownEnds > BigInt(latestBlock.timestamp)) {
    throw new Error("Debt owner is inside the 20-minute hold period");
  }
}

async function projectedFullDebt(ownerAddress, bufferSeconds = 60) {
  const latestBlock = await provider.getBlock("latest");
  if (latestBlock === null) throw new Error("Could not load latest block");

  return protocolReader.debtBalanceAtTimestamp(
    ownerAddress,
    cUSDCAddress,
    latestBlock.timestamp + bufferSeconds
  );
}

async function approveUSDCIfNeeded(payerAddress, approvalAmount) {
  if (approvalAmount === 0n) throw new Error("No outstanding debt to repay");

  const currentAllowance = await USDC.allowance(payerAddress, cUSDCAddress);
  if (currentAllowance < approvalAmount) {
    const approveTx = await USDC.approve(cUSDCAddress, approvalAmount);
    await approveTx.wait();
  }
}
```

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

```js
async function repayUSDCDebt(amount) {
  const userAddress = await wallet.getAddress();
  const USDCDecimals = await USDC.decimals();

  const isFullRepay = amount === "full";
  const repayArg = isFullRepay
    ? 0n
    : ethers.parseUnits(amount.toString(), USDCDecimals);

  if (!isFullRepay && repayArg === 0n) {
    throw new Error("Repay amount must be greater than zero");
  }

  await assertRepayCooldownHasPassed(userAddress);

  const approvalAmount = isFullRepay
    ? await projectedFullDebt(userAddress)
    : repayArg;

  await approveUSDCIfNeeded(userAddress, approvalAmount);

  // Pass 0 to clear the entire outstanding balance after on-chain accrual.
  const repayTx = await cUSDC.repay(repayArg);
  const receipt = await repayTx.wait();

  console.log(`Repaid ${isFullRepay ? "full debt" : amount + " USDC"}`);
  return receipt;
}
```

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.

```js
async function repayForAccount(ownerAddress, amount) {
  const payerAddress = await wallet.getAddress();
  const USDCDecimals = await USDC.decimals();

  const isFullRepay = amount === "full";
  const repayArg = isFullRepay
    ? 0n
    : ethers.parseUnits(amount.toString(), USDCDecimals);

  if (!isFullRepay && repayArg === 0n) {
    throw new Error("Repay amount must be greater than zero");
  }

  await assertRepayCooldownHasPassed(ownerAddress);

  const approvalAmount = isFullRepay
    ? await projectedFullDebt(ownerAddress)
    : repayArg;

  await approveUSDCIfNeeded(payerAddress, approvalAmount);

  // Argument order is (assets, owner). Passing 0 fully repays owner debt.
  const repayTx = await cUSDC.repayFor(repayArg, ownerAddress);
  const receipt = await repayTx.wait();

  console.log(
    `Repaid ${isFullRepay ? "full debt" : amount + " USDC"} for ${ownerAddress}`
  );
  return receipt;
}
```

{% hint style="info" %}
**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.
{% endhint %}

***

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

***


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

***


# Leverage

Leveraging in Curvance lets an account increase exposure inside one isolated market by borrowing against posted collateral, swapping the borrowed asset, and depositing the output as more collateral.

Curvance's Position Managers wrap that borrow, swap, and deposit flow into one atomic transaction. The transaction still runs the same market checks for borrowability, caps, collateral value, price availability, and liquidity, but the user does not need to manually borrow, swap, deposit, check health, and repeat.

This overview sits above the Leveraging and Deleveraging subpages. It covers the shared structs, swap rules, environment setup, and health reads that both flows use.

For a deeper dive, see [Position Managers](/app/developer-docs/protocol-managers).

***

### Core Structures for Leveraging

#### `LeverageAction` (`IPositionManager.sol:25`)

```solidity
struct LeverageAction {
    IBorrowableCToken borrowableCToken; // cToken to borrow from.
    uint256 borrowAssets;               // Amount of underlying to borrow.
    ICToken cToken;                     // cToken that receives the swapped output as collateral.
    uint256 expectedShares;             // Minimum shares expected from the collateral deposit.
    SwapperLib.Swap swapAction;         // Swap from debt asset into collateral-side input.
    bytes auxData;                      // Optional PositionManager-specific data.
}
```

#### `DeleverageAction` (`IPositionManager.sol:47`)

```solidity
struct DeleverageAction {
    ICToken cToken;                     // cToken being unwound.
    uint256 collateralAssets;           // Amount of cToken underlying to withdraw.
    IBorrowableCToken borrowableCToken; // cToken whose debt will be repaid.
    uint256 repayAssets;                // Minimum debt asset amount required after swaps.
    SwapperLib.Swap[] swapActions;      // Swap path from collateral output into debt asset.
    bytes auxData;                      // Optional PositionManager-specific data.
}
```

{% hint style="info" %}
`repayAssets` is a minimum amount that must be held by the PositionManager after the deleverage swap path. The PositionManager then repays as much of the owner's debt as possible, up to the current debt balance, and sends any leftover debt or collateral asset back to the owner.
{% endhint %}

#### `SwapperLib.Swap` (`SwapperLib.sol:36`)

```solidity
struct Swap {
    address inputToken;   // Token being swapped from.
    uint256 inputAmount;  // Amount of inputToken to swap.
    address outputToken;  // Token being swapped to.
    address target;       // Swap router or aggregator address.
    uint256 slippage;     // WAD-scaled value-loss tolerance for _swapSafe.
    bytes call;           // Encoded call sent to target.
}
```

{% hint style="danger" %}
`LeverageAction` has one `swapAction`. `DeleverageAction` has `swapActions[]`, but multi-hop behavior depends on the specific PositionManager. For example, `SimplePositionManager` requires exactly one deleverage swap when the collateral and debt assets differ, while Pendle and LP-style managers can use multiple swaps after their protocol-specific exit step.
{% endhint %}

***

### Swap Rules

#### Leveraging

When leveraging, a swap converts the borrowed (debt) asset into the collateral asset. Populate the `call` field of `swapAction` with your aggregator's calldata (Kyber, 1inch, Odos, Paraswap, etc.).&#x20;

The swap output must be routed back to the PositionManager. `SwapperLib` calls the configured calldata checker with `expectedRecipient = address(this)`, so calldata with another recipient reverts.

`swapAction.target` must have an external calldata checker configured in the Central Registry. If `centralRegistry.externalCalldataChecker(swapAction.target)` is zero, `SwapperLib` reverts with `SwapperLib__UnknownCalldata()`.

#### Deleveraging

During deleveraging, swaps convert the collateral asset into the debt asset. The `call` bytes in each swap entry are calldata for `swapAction.target`; the `target` field is the router or aggregator address. The encoded swap recipient still needs to be the PositionManager.

Treat Position Managers as stateless routers. Do not leave user-specific balances on them between calls. For vault and multi-hop deleverage paths, encode each `swapAction.inputAmount` to match the amount that the previous step actually produces.

#### Auxiliary Data

Some assets require extra router-specific instructions to enter or exit. For example, entering a Pendle PT position requires calldata for Pendle's internal SY-mint flow in addition to the underlying  aggregator swap. It is empty for simple ERC20 leverage.

For Pendle PT leverage, `PendlePTPositionManager` decodes `auxData` as:

```solidity
(address lpToken, uint256 minPtAmount, PendleLib.PendleAction pendleAction)
```

The supplied draft encoded the Pendle PT tuple as `(PendleAction, address, uint256)`, which would not match the on-chain decoder. Keep the tuple order in sync with the specific PositionManager contract you call

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

const pendleLpMarketAddress = "0x...";
const minPtAmount = ethers.parseUnits("9.8", 18);
const INPUT_TOKEN_DECIMALS = 18;
const SY_INPUT_TOKEN_ADDRESS = "0x...";
const SY_MINT_TOKEN_ADDRESS = "0x...";
const PENDLE_SWAP_ADDRESS = "0x...";

const SWAP_DATA_TUPLE =
  "tuple(uint8 swapType,address extRouter,bytes extCalldata,bool needScale)";
const APPROX_TUPLE =
  "tuple(uint256 guessMin,uint256 guessMax,uint256 guessOffchain,uint256 maxIteration,uint256 eps)";
const TOKEN_INPUT_TUPLE =
  `tuple(address tokenIn,uint256 netTokenIn,address tokenMintSy,address pendleSwap,${SWAP_DATA_TUPLE} swapData)`;
const TOKEN_OUTPUT_TUPLE =
  `tuple(address tokenOut,uint256 minTokenOut,address tokenRedeemSy,address pendleSwap,${SWAP_DATA_TUPLE} swapData)`;

// Use empty limit-order arrays unless your Pendle route intentionally uses them.
const LIMIT_ORDER_DATA_TUPLE =
  "tuple(address limitRouter,uint256 epsSkipMarket,tuple(tuple(uint256 salt,uint256 expiry,uint256 nonce,uint8 orderType,address token,address YT,address maker,address receiver,uint256 makingAmount,uint256 lnImpliedRate,uint256 failSafeRate,bytes permit) order,bytes signature,uint256 makingAmount)[] normalFills,tuple(tuple(uint256 salt,uint256 expiry,uint256 nonce,uint8 orderType,address token,address YT,address maker,address receiver,uint256 makingAmount,uint256 lnImpliedRate,uint256 failSafeRate,bytes permit) order,bytes signature,uint256 makingAmount)[] flashFills,bytes optData)";

const PENDLE_ACTION_TUPLE =
  `tuple(${APPROX_TUPLE} approx,${TOKEN_INPUT_TUPLE} input,${TOKEN_OUTPUT_TUPLE} output,${LIMIT_ORDER_DATA_TUPLE} limit)`;

const pendleAction = {
  approx: {
    guessMin: ethers.parseUnits("9.9", 18),
    guessMax: ethers.parseUnits("10.0", 18),
    guessOffchain: 0n,
    maxIteration: 30n,
    eps: ethers.parseUnits("0.001", 18)
  },
  input: {
    tokenIn: SY_INPUT_TOKEN_ADDRESS,
    netTokenIn: ethers.parseUnits("10", INPUT_TOKEN_DECIMALS),
    tokenMintSy: SY_MINT_TOKEN_ADDRESS,
    pendleSwap: PENDLE_SWAP_ADDRESS,
    swapData: {
      swapType: 0, // NONE. Use another enum value only when Pendle should call its aggregator.
      extRouter: ethers.ZeroAddress,
      extCalldata: "0x",
      needScale: false
    }
  },
  output: {
    tokenOut: ethers.ZeroAddress,
    minTokenOut: 0n,
    tokenRedeemSy: ethers.ZeroAddress,
    pendleSwap: ethers.ZeroAddress,
    swapData: {
      swapType: 0,
      extRouter: ethers.ZeroAddress,
      extCalldata: "0x",
      needScale: false
    }
  },
  limit: {
    limitRouter: ethers.ZeroAddress,
    epsSkipMarket: 0n,
    normalFills: [],
    flashFills: [],
    optData: "0x"
  }
};

const abiCoder = ethers.AbiCoder.defaultAbiCoder();

// Pendle PT PositionManager leverage auxData:
const auxData = abiCoder.encode(
  ["address", "uint256", PENDLE_ACTION_TUPLE],
  [pendleLpMarketAddress, minPtAmount, pendleAction]
);
```

If the PositionManager first runs a Curvance `swapAction` before entering Pendle, `swapAction.outputToken` must match `pendleAction.input.tokenIn` for Pendle PT, or a valid SY input token for Pendle LP.

***

### Setting Up Your Environment

These examples use 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 signer = new ethers.Wallet("YOUR_PRIVATE_KEY", provider);
const userAddress = await signer.getAddress();

const MARKET_MANAGER = "0x...";
const CENTRAL_REGISTRY = "0x...";
const POSITION_MANAGER = "0x...";
const C_TOKEN_COLLATERAL = "0x...";
const C_TOKEN_DEBT = "0x...";
const PROTOCOL_READER = "0x...";
const APPROVED_SWAP_TARGET = "0x...";

const ERC20_ABI = [
  "function allowance(address owner,address spender) view returns (uint256)",
  "function approve(address spender,uint256 amount) returns (bool)",
  "function decimals() view returns (uint8)"
];

const marketManager = new ethers.Contract(MARKET_MANAGER, MARKET_MANAGER_ABI, provider);
const centralRegistry = new ethers.Contract(CENTRAL_REGISTRY, CENTRAL_REGISTRY_ABI, provider);
const positionManager = new ethers.Contract(POSITION_MANAGER, POSITION_MANAGER_ABI, signer);
const collateralCToken = new ethers.Contract(C_TOKEN_COLLATERAL, CTOKEN_ABI, provider);
const debtCToken = new ethers.Contract(C_TOKEN_DEBT, CTOKEN_ABI, provider);
const protocolReader = new ethers.Contract(PROTOCOL_READER, PROTOCOL_READER_ABI, provider);
```

Real PositionManager function signatures (from `BasePositionManager.sol`):

```solidity
function depositAndLeverage(uint256 assets, LeverageAction action, uint256 slippage) external;
function leverage(LeverageAction action, uint256 slippage) external;
function leverageFor(LeverageAction action, address account, uint256 slippage) external;
function deleverage(DeleverageAction action, uint256 slippage) external;
```

`slippage` is WAD-scaled for the PositionManager's net position-value sanity check. A common UI value is `ethers.parseUnits("0.01", 18)` for 1%. The slippage check is enforced by a `checkSlippage` modifier that snapshots position value before and after and reverts if the loss exceeds the configured tolerance.

{% hint style="info" %}
`leverageFor` and `deleverageFor` (delegation variants) require the account to have pre-approved the caller via `setDelegateApproval` on the cTokens involved. See the [Integration Cookbook: Collateralizing on Behalf of Users ](/app/developer-docs/quick-start-guides/integration-cookbook#collateralizing-on-behalf-of-users)for the delegation model.
{% endhint %}

The PositionManager `checkSlippage` modifier snapshots `marketManager.statusOf(account)` before and after the action and reverts if the account's net value loss exceeds the supplied tolerance. The contract comments describe this as a sanity check, not a replacement for route quotes, min-out checks, `expectedShares`, or swap-level slippage checks.

PositionManager callbacks also apply `centralRegistry.protocolLeverageFee()` before the internal swap or vault operation. The top-level action amount still needs to match the amount borrowed or withdrawn, but the swap path should be quoted against the net amount after this fee.

***

### Simple PositionManager Flow

Use `SimplePositionManager` for generic ERC20-to-ERC20 leverage where the cToken's underlying asset is the final collateral asset. In this path, the debt asset and collateral asset must differ, and a leverage-in action always needs one swap.

Simple leverage-in rules:

* `action.borrowAssets` is the gross amount borrowed from `borrowableCToken`.
* `swapAction.inputAmount` is the net amount after the PositionManager fee.
* `swapAction.inputToken` is `action.borrowableCToken.asset()`.
* `swapAction.outputToken` is `action.cToken.asset()`.
* `auxData` is `0x`.

```js
const WAD = 10n ** 18n;
const ONE_PERCENT = ethers.parseUnits("0.01", 18);

async function assertLeveragePreconditions() {
  const [isPM, debtCap, [, , borrowPaused]] = await Promise.all([
    marketManager.isPositionManager(POSITION_MANAGER),
    marketManager.debtCaps(C_TOKEN_DEBT),
    marketManager.actionsPaused(C_TOKEN_DEBT)
  ]);

  if (!isPM) throw new Error("PositionManager is not enabled for this market");
  if (debtCap === 0n) throw new Error("Debt cToken is not borrowable in this market");
  if (borrowPaused) throw new Error("Borrowing is paused for the debt cToken");
}

async function buildSimpleLeverageAction({
  borrowAmount,
  quotedCollateralOut,
  swapCalldata
}) {
  await assertLeveragePreconditions();
  const netBorrowAmount = await netAfterPositionManagerFee(borrowAmount);

  const [debtAsset, collateralAsset] = await Promise.all([
    debtCToken.asset(),
    collateralCToken.asset()
  ]);

  const expectedShares = await collateralCToken.previewDeposit(quotedCollateralOut);

  return {
    borrowableCToken: C_TOKEN_DEBT,
    borrowAssets: borrowAmount,
    cToken: C_TOKEN_COLLATERAL,
    expectedShares,
    swapAction: {
      inputToken: debtAsset,
      inputAmount: netBorrowAmount,
      outputToken: collateralAsset,
      target: APPROVED_SWAP_TARGET,
      slippage: ONE_PERCENT,
      call: swapCalldata
    },
    auxData: "0x"
  };
}

async function leverageExistingPosition(action) {
  const tx = await positionManager.leverage(action, ONE_PERCENT);
  return tx.wait();
}
```

&#x20;`expectedShares` is enforced after the swap and deposit: if the deposit returns fewer shares than `action.expectedShares`, the PositionManager reverts with `BasePositionManager__InvalidSlippage()`.

Simple deleverage uses the same shape in reverse, but `DeleverageAction.swapActions` must contain exactly one swap. The top-level `collateralAssets` is the gross cToken underlying amount withdrawn through `withdrawByPositionManager`; the swap's `inputAmount` should be the net amount after the PositionManager fee.

***

### Vault PositionManager Flows

Vault PositionManagers handle cTokens whose underlying asset is a vault share or receipt token. The leverage-in shape is:

1. Borrow the debt asset.
2. Optionally swap the debt asset into the vault's deposit asset.
3. Deposit into the vault to receive vault shares or receipt tokens.
4. Deposit those vault shares or receipt tokens into the Curvance cToken as collateral.

#### Single-Sided Vaults

&#x20;`SingleSidedVaultPositionManager` handles non-native vault deposits. If the borrowed asset already matches the vault's underlying asset, it skips the Curvance swap and deposits directly into the vault. Otherwise it requires one swap from the debt asset into the vault underlying.

This manager inherits the simple deleverage path. That means deleverage swaps the vault share or receipt token through an aggregator, rather than redeeming the vault first. Use this shape for vault collateral that cannot be instantly redeemed on the deleverage path.

```js
const VAULT_ABI = [
  "function asset() view returns (address)"
];

function sameAddress(a, b) {
  return a.toLowerCase() === b.toLowerCase();
}

function emptySwapAction() {
  return {
    inputToken: ethers.ZeroAddress,
    inputAmount: 0n,
    outputToken: ethers.ZeroAddress,
    target: ethers.ZeroAddress,
    slippage: 0n,
    call: "0x"
  };
}

async function buildSingleSidedVaultLeverageAction({
  borrowAmount,
  vaultAddress,
  expectedVaultShares,
  swapCalldata
}) {
  await assertLeveragePreconditions();

  const [debtAsset, vaultUnderlying] = await Promise.all([
    debtCToken.asset(),
    new ethers.Contract(vaultAddress, VAULT_ABI, provider).asset()
  ]);

  const netBorrowAmount = await netAfterPositionManagerFee(borrowAmount);
  const needsSwap = !sameAddress(debtAsset, vaultUnderlying);

  const expectedShares = await collateralCToken.previewDeposit(expectedVaultShares);

  return {
    borrowableCToken: C_TOKEN_DEBT,
    borrowAssets: borrowAmount,
    cToken: C_TOKEN_COLLATERAL,
    expectedShares,
    swapAction: needsSwap
      ? {
          inputToken: debtAsset,
          inputAmount: netBorrowAmount,
          outputToken: vaultUnderlying,
          target: APPROVED_SWAP_TARGET,
          slippage: ONE_PERCENT,
          call: swapCalldata
        }
      : emptySwapAction(),
    auxData: "0x"
  };
}
```

`expectedVaultShares` should come from your route quote or simulation for the vault deposit. The cToken-level `expectedShares` then uses `previewDeposit(expectedVaultShares)` because the final Curvance deposit receives vault shares or receipt tokens.

***

#### Native Vaults

`NativeVaultPositionManager` is for vaults that take the chain's native gas token. If the debt asset is the wrapped native token, the manager unwraps it and deposits native directly. Otherwise it requires one swap from the debt asset into the native token. In that swap, `swapAction.outputToken` may be `address(0)` or `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`.

```js
const NATIVE_TOKEN = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";

async function buildNativeVaultLeverageAction({
  borrowAmount,
  wrappedNative,
  expectedVaultShares,
  swapCalldata
}) {
  await assertLeveragePreconditions();

  const debtAsset = await debtCToken.asset();
  const netBorrowAmount = await netAfterPositionManagerFee(borrowAmount);
  const needsSwap = !sameAddress(debtAsset, wrappedNative);
  const expectedShares = await collateralCToken.previewDeposit(expectedVaultShares);

  return {
    borrowableCToken: C_TOKEN_DEBT,
    borrowAssets: borrowAmount,
    cToken: C_TOKEN_COLLATERAL,
    expectedShares,
    swapAction: needsSwap
      ? {
          inputToken: debtAsset,
          inputAmount: netBorrowAmount,
          outputToken: NATIVE_TOKEN,
          target: APPROVED_SWAP_TARGET,
          slippage: ONE_PERCENT,
          call: swapCalldata
        }
      : emptySwapAction(),
    auxData: "0x"
  };
}
```

***

### Monitoring Position Health

A healthy leverage ratio is essential to avoid liquidation. Use `ProtocolReader.getPositionHealth()` to check an existing position or simulate a hypothetical action.

**Interpretation:**

* `positionHealth` is WAD-scaled. `positionHealth >= 1e18` means the position is at or above the soft liquidation threshold. `positionHealth < 1e18` means it is liquidatable.
* `errorCodeHit` is `true` if the reader encountered a pricing or oracle error; treat a `true` value as "result is unreliable; stop and investigate."

**Simulating vs inspecting:**

* Pass **zero addresses and zero amounts** for the `cToken` / `borrowableCToken` / `collateralAssets` / `debtAssets` parameters to simply inspect the current position.
* Pass a non-zero `cToken` with `collateralAssets > 0` and `isDeposit = true` to simulate "what would my health be if I deposited `collateralAssets` of `cToken`?"
* Pass a non-zero `borrowableCToken` with `debtAssets > 0` and `isRepayment = false` to simulate "what would my health be if I borrowed `debtAssets`?"
* In `getPositionHealth`, `bufferTime` is used only by the debt-side hypothetical branch. For projected aggregate debt and leverage sizing, use `hypotheticalLeverageOf`, `getLeverageSnapshot`, or `debtBalanceAtTimestamp` where appropriate.

```js
const HEALTH_WAD = 10n ** 18n;

function formatHealth(positionHealth) {
  if (positionHealth === ethers.MaxUint256) return "no debt";
  return `${ethers.formatUnits(positionHealth, 18)}x`;
}

async function checkPositionHealth() {
  const [positionHealth, errorCodeHit] =
    await protocolReader.getPositionHealth(
      MARKET_MANAGER,
      userAddress,
      ethers.ZeroAddress,
      ethers.ZeroAddress,
      false,
      0n,
      false,
      0n,
      0n
    );

  if (errorCodeHit) {
    throw new Error("Oracle or reader error hit; health value is unreliable");
  }

  const healthy = positionHealth >= HEALTH_WAD;
  console.log(`Position health: ${formatHealth(positionHealth)}`);

  return { positionHealth, healthy };
}
```

For leverage sizing before a new deposit, `hypotheticalLeverageOf(account, cToken, borrowableCToken, assets, bufferTime)` returns current leverage, theoretical maximum leverage, adjusted maximum leverage, and the maximum borrowable debt amount after cap, collateral-cap, and liquidity limits. Its own comments warn that it can overestimate executable leverage when AMM fees and slippage apply.

***

### Common Error Scenarios

| Error                                                   | Meaning                                                                                                                                  |
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `BasePositionManager__Unauthorized()`                   | The cToken or borrowable cToken is not listed for the PositionManager's MarketManager, or a callback came from an unexpected caller.     |
| `BasePositionManager__InvalidParam()`                   | The action fields do not match the callback, swap path, target, input token, output token, amount, or PositionManager-specific aux data. |
| `BasePositionManager__InvalidAmount()`                  | A required amount was zero, such as `repayAssets == 0` for deleverage.                                                                   |
| `BasePositionManager__InvalidSlippage()`                | The net value sanity check failed, or a leverage deposit returned fewer shares than `expectedShares`.                                    |
| `BasePositionManager__InsufficientAssetsForRepayment()` | Deleverage produced less debt asset than `repayAssets`.                                                                                  |
| `SwapperLib__UnknownCalldata()`                         | `swapAction.target` has no external calldata checker configured in the Central Registry.                                                 |
| `SwapperLib__Slippage(uint256)`                         | Swap-level value loss exceeded `swapAction.slippage`, or `swapAction.slippage >= 1e18`.                                                  |
| `SwapperLib__TokenPrice(address)`                       | The swap-level oracle price read failed.                                                                                                 |
| `PluginDelegable__Unauthorized()`                       | A delegate called `leverageFor` or `deleverageFor` without PositionManager delegation from the account.                                  |
| `MarketManager__CapReached()`                           | The borrow would exceed the configured `debtCaps(cToken)`.                                                                               |

For ethers v6 integrations, decode reverts with the `iface.parseError(errData)` pattern from Borrow: Error Handling. Include the PositionManager ABI, MarketManager ABI, cToken ABI, ProtocolReader ABI, and small ABI fragments for `SwapperLib` and `PluginDelegable` errors if your generated ABIs do not include them.


# Leveraging

This page covers two concrete flows. Both extend the background in the Leveraging intro:

1. Deposit underlying and immediately leverage in a single transaction (building from scratch).
2. Leverage an existing Curvance position (add leverage without a fresh deposit).

This page assumes you already read Leverage, which covers `LeverageAction`, `SwapperLib.Swap`, swap calldata, auxiliary data, PositionManager fees, and position-health reads.

***

### 1. Initial Deposit and Leverage

The most common approach is to deposit and leverage in a single transaction. Ideal for users building a leveraged position from scratch.

Because `depositAndLeverage` applies the `preDeposit` modifier before `checkSlippage`, the initial deposit is posted before the PositionManager snapshots account status. The top-level `slippage` check is a net position-value sanity check for the leverage leg after the initial collateral is already in the account.

#### Preparing for Leverage

Before calling `depositAndLeverage`, prepare the market and route:

1. **Understand your target position:** Choose the isolated market, collateral cToken, debt cToken, and target leverage. Confirm the debt cToken is actually borrowable by checking `await marketManager.debtCaps(debtCToken) > 0n`. The market name is a grouping, not a role assignment.
2. **Build aggregator calldata** whose swap recipient is the PositionManager. Curvance calldata checkers validate the recipient against the PositionManager address.
3. **Set a slippage tolerance:** Set three slippage controls: the aggregator route's own min-out, `swapAction.slippage` for `SwapperLib._swapSafe`, and the top-level PositionManager `slippage` checked by `checkSlippage`.
4. **Approve the PositionManager:** your underlying collateral token must grant allowance to the PositionManager contract.

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

const ERC20_ABI = [
  "function allowance(address owner,address spender) view returns (uint256)",
  "function approve(address spender,uint256 amount) returns (bool)"
];

const POSITION_MANAGER = "0x...";
const COLLATERAL_ASSET_UNDERLYING = "0x...";
const COLLATERAL_DECIMALS = 18;

const underlying = new ethers.Contract(
  COLLATERAL_ASSET_UNDERLYING,
  ERC20_ABI,
  signer
);

const userAddress = await signer.getAddress();
const depositAmount = ethers.parseUnits("1000", COLLATERAL_DECIMALS);

const currentAllowance = await underlying.allowance(
  userAddress,
  POSITION_MANAGER
);

if (currentAllowance < depositAmount) {
  const approveTx = await underlying.approve(POSITION_MANAGER, depositAmount);
  await approveTx.wait();
}
```

#### Constructing the `LeverageAction`

`LeverageAction.borrowAssets` is the gross amount borrowed from the debt cToken. The internal swap usually uses the net amount after `centralRegistry.protocolLeverageFee()`, because the PositionManager deducts that fee before calling the manager-specific swap logic.

For a simple ERC20 PositionManager, `swapAction.inputAmount` should equal the net borrowed amount after the PositionManager fee, not always the gross `borrowAssets`.

```js
const BPS = 10_000n;
const WAD_SLIPPAGE_ONE_PERCENT = ethers.parseUnits("0.01", 18);

const MARKET_MANAGER_ABI = [
  "function debtCaps(address cToken) view returns (uint256)",
  "function isPositionManager(address positionManager) view returns (bool)",
  "function actionsPaused(address cToken) view returns (bool,bool,bool)"
];

const POSITION_MANAGER_ABI = [
  "function depositAndLeverage(uint256 assets,(address borrowableCToken,uint256 borrowAssets,address cToken,uint256 expectedShares,(address inputToken,uint256 inputAmount,address outputToken,address target,uint256 slippage,bytes call) swapAction,bytes auxData) action,uint256 slippage)",
  "function leverage((address borrowableCToken,uint256 borrowAssets,address cToken,uint256 expectedShares,(address inputToken,uint256 inputAmount,address outputToken,address target,uint256 slippage,bytes call) swapAction,bytes auxData) action,uint256 slippage)",
  "function leverageFor((address borrowableCToken,uint256 borrowAssets,address cToken,uint256 expectedShares,(address inputToken,uint256 inputAmount,address outputToken,address target,uint256 slippage,bytes call) swapAction,bytes auxData) action,address account,uint256 slippage)",
  "function centralRegistry() view returns (address)",
  "function setDelegateApproval(address delegate,bool isApproved)"
];

const CTOKEN_ABI = [
  "function asset() view returns (address)",
  "function previewDeposit(uint256 assets) view returns (uint256)",
  "function collateralPosted(address account) view returns (uint256)",
  "function balanceOf(address account) view returns (uint256)",
  "function postCollateral(uint256 shares)"
];

const CENTRAL_REGISTRY_ABI = [
  "function protocolLeverageFee() view returns (uint256)"
];

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

async function netAfterPositionManagerFee(positionManager, grossAmount) {
  const centralRegistryAddress = await positionManager.centralRegistry();
  const centralRegistry = new ethers.Contract(
    centralRegistryAddress,
    CENTRAL_REGISTRY_ABI,
    signer
  );

  const feeBps = await centralRegistry.protocolLeverageFee();
  const fee = mulDivUp(grossAmount, feeBps, BPS);
  if (fee >= grossAmount) throw new Error("PositionManager fee exceeds amount");
  return grossAmount - fee;
}
```

```js
const MARKET_MANAGER = "0x...";
const BORROWABLE_CTOKEN = "0x...";
const COLLATERAL_CTOKEN = "0x...";
const AGGREGATOR_ROUTER_ADDRESS = "0x...";
const DEBT_DECIMALS = 6;
const ESTIMATED_COLLATERAL_OUT = ethers.parseUnits("0.49", 18);

const marketManager = new ethers.Contract(
  MARKET_MANAGER,
  MARKET_MANAGER_ABI,
  provider
);
const positionManager = new ethers.Contract(
  POSITION_MANAGER,
  POSITION_MANAGER_ABI,
  signer
);
const debtCToken = new ethers.Contract(BORROWABLE_CTOKEN, CTOKEN_ABI, provider);
const collateralCToken = new ethers.Contract(
  COLLATERAL_CTOKEN,
  CTOKEN_ABI,
  provider
);

const [isPositionManager, debtCap, [, , borrowPaused]] = await Promise.all([
  marketManager.isPositionManager(POSITION_MANAGER),
  marketManager.debtCaps(BORROWABLE_CTOKEN),
  marketManager.actionsPaused(BORROWABLE_CTOKEN)
]);

if (!isPositionManager) throw new Error("PositionManager is not enabled");
if (debtCap === 0n) throw new Error("Debt cToken is not borrowable");
if (borrowPaused) throw new Error("Borrowing is paused for this debt cToken");

const [debtAsset, collateralAsset] = await Promise.all([
  debtCToken.asset(),
  collateralCToken.asset()
]);

const borrowAssets = ethers.parseUnits("500", DEBT_DECIMALS);
const netSwapInput = await netAfterPositionManagerFee(
  positionManager,
  borrowAssets
);

const quotedShares = await collateralCToken.previewDeposit(
  ESTIMATED_COLLATERAL_OUT
);
const expectedShares = quotedShares * 9950n / 10_000n; // 0.50% share buffer.

// Aggregator calldata must send output to POSITION_MANAGER.
const swapCalldata = "0x...";

const swapAction = {
  inputToken: debtAsset,
  inputAmount: netSwapInput,
  outputToken: collateralAsset,
  target: AGGREGATOR_ROUTER_ADDRESS,
  slippage: WAD_SLIPPAGE_ONE_PERCENT,
  call: swapCalldata
};

const leverageAction = {
  borrowableCToken: BORROWABLE_CTOKEN,
  borrowAssets,
  cToken: COLLATERAL_CTOKEN,
  expectedShares,
  swapAction,
  auxData: "0x"
};
```

**Computing `expectedShares`:** the simplest approach is `await cToken.previewDeposit(estimatedSwapOutAmount)`, where `estimatedSwapOutAmount` is what you expect the aggregator to return. For aux-swap assets (Pendle PT, LP tokens), the input to `previewDeposit` is the post-aux-swap asset amount, not the raw borrowed amount. Slight staleness is acceptable here; the `checkSlippage` modifier is what ultimately enforces correctness.

{% hint style="info" %}
For the simple ERC20 path, `swapAction.outputToken` is `action.cToken.asset()`. For auxiliary managers such as Pendle or vault PositionManagers, `swapAction.outputToken` can be an intermediate protocol input, and `auxData` controls the final mint or enter step. Keep the action shape in sync with the specific PositionManager contract.
{% endhint %}

#### Executing the Leverage Operation

```js
// Slippage for the overall leverage action (WAD-scaled). 1e16 = 1%.
const slippage = ethers.parseUnits('0.01', 18);

// Execute deposit and leverage in one transaction
const tx = await positionManager.depositAndLeverage(
  depositAmount,
  leverageAction,
  slippage,
  { gasLimit: 2_000_000n } // optional overrides, not a positional arg
);

const receipt = await tx.wait();
console.log(`Leveraged position created. Tx hash: ${receipt.hash}`);
```

`expectedShares` is enforced after the swap output is deposited. If the deposit returns fewer shares than `action.expectedShares`, the PositionManager reverts with `BasePositionManager__InvalidSlippage()`.

***

### 2. Leveraging an Existing Position

If the account already has enough posted collateral in the same isolated market, call `leverage(LeverageAction action, uint256 slippage)` to add leverage without a fresh deposit.

```js
const postedOnTarget = await collateralCToken.collateralPosted(userAddress);
const targetBalance = await collateralCToken.balanceOf(userAddress);
const idleTargetShares = targetBalance - postedOnTarget;

if (postedOnTarget === 0n && idleTargetShares > 0n) {
  const postTx = await collateralCToken.postCollateral(idleTargetShares);
  await postTx.wait();
}
```

**Prerequisite:** the collateral must already be posted as collateral in the market (`collateralPosted[owner] > 0` on the collateral `cToken`). If you only deposited without posting, call `postCollateral(shares)` on the collateral cToken first, or use `depositAsCollateral` / `depositAndLeverage` in the initial flow.&#x20;

`postCollateral(shares)` takes cToken shares, not underlying assets. If the user only has underlying collateral tokens and no cToken shares, use `depositAndLeverage` for the initial flow, or deposit first and then post the received shares.

You can simulate the resulting health before submitting:

```js
const PROTOCOL_READER = "0x...";
const PROTOCOL_READER_ABI = [
  "function getPositionHealth(address mm,address account,address cToken,address borrowableCToken,bool isDeposit,uint256 collateralAssets,bool isRepayment,uint256 debtAssets,uint256 bufferTime) view returns (uint256,bool)"
];

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

const [positionHealth, oracleError] =
  await protocolReader.getPositionHealth(
    MARKET_MANAGER,
    userAddress,
    COLLATERAL_CTOKEN,
    BORROWABLE_CTOKEN,
    true,
    ESTIMATED_COLLATERAL_OUT,
    false,
    borrowAssets,
    0
  );

if (oracleError) throw new Error("Oracle error while simulating position");
if (positionHealth <= ethers.parseUnits("1", 18)) {
  throw new Error("Projected position is at or below soft liquidation health");
}
```

Then execute:

```js
const tx = await positionManager.leverage(
  leverageAction,
  actionSlippage,
  { gasLimit: 2_000_000n }
);

const receipt = await tx.wait();
console.log(`Position further leveraged: ${receipt.hash}`);
```

***

### Hold-Period Interaction

Posting collateral and borrowing both reset the market's 20-minute `MIN_HOLD_PERIOD` for the account. `depositAndLeverage` posts collateral and borrows, so it starts or resets the cooldown. `leverage` borrows and deposits more collateral through the PositionManager, so it also starts or resets the cooldown.

During the cooldown, repayment, redemption / withdrawal, and cToken transfers for that account in the same isolated market revert with `MarketManager__MinimumHoldPeriod()`. Additional borrows and additional collateral postings are not directly blocked by the hold-period check, but they reset the cooldown again.

Deleveraging during the cooldown reverts. In the PositionManager deleverage path, `repayFor` checks the owner's hold period, and the final redemption review also checks transfer and redeem eligibility.

***

### Important Considerations

#### Position health and risk management

* Monitor the health factor before **and** after the operation using `ProtocolReader.getPositionHealth(...)`. See Monitoring Position Health for the WAD-scaled interpretation.
* **Max leverage ratio is not a single global parameter.** There is no single global max leverage ratio. The practical ceiling depends on per-market collateral config, borrow caps, available liquidity, prices, swap slippage, and PositionManager fees.
* For sizing leverage from a new deposit, `ProtocolReader.hypotheticalLeverageOf(...)` can estimate max borrow and leverage, but its own comments warn that AMM fees and slippage can make the executable amount lower.
* Liquidation penalties are configured per token through liquidation incentive and close-factor curves. The curve depends on how far the account is into liquidation and can also use dynamic auction configuration.

#### Swap data configuration

* The aggregator calldata (`swapAction.call`) must set its **recipient** to the PositionManager contract so the swapped collateral can be re-deposited in the same transaction.
* &#x20;`swapAction.inputAmount` must match the amount the specific PositionManager will actually swap. In the simple leverage path, that is the gross borrow amount minus `protocolLeverageFee()`.
* Typical aggregator slippage: 0.3-1% for stable pairs, 1-3% for volatile pairs. This is separate from the `slippage` arg passed to `depositAndLeverage` / `leverage`, which is the whole-action WAD-scaled tolerance checked by the `checkSlippage` modifier.
* The swap target must have a configured external calldata checker in the Central Registry. Unknown swap targets revert before execution.

#### Amount calculations

* Pre-compute `expectedShares` via `await cToken.previewDeposit(estimatedSwapOut)`; small staleness is fine since the outer slippage check enforces correctness.
* Include aggregator fees in your target leverage calc; for aux-swap assets (Pendle, LPs), include any additional fees from the underlying protocol's mint/swap.

#### Protocol-specific features

* Some managers require populated `auxData`, including Pendle and several vault or LP managers. Do not infer the tuple shape from another manager. Check the exact PositionManager contract or use the examples in Leverage.
* For simple ERC20 leverage, `estimatedCollateralInput` is the expected swap output of `action.cToken.asset()`. For aux-swap assets such as Pendle PT or LP positions, it is the expected final cToken underlying amount after the auxiliary enter step, not the raw borrowed amount.
* Apply a buffer to `expectedShares` so normal quote movement, protocol fees, swap fees, and cToken exchange-rate movement do not cause avoidable reverts.
* Do not default to `"0x"` for a manager that decodes `auxData`; that will revert during ABI decode.

#### Delegated leverage

For platforms leveraging on behalf of users, call `leverageFor(LeverageAction action, address account, uint256 slippage)`.

The account must approve the delegate on the PositionManager contract:

```js
const delegateAddress = "0x...";

const approvalTx = await positionManager.setDelegateApproval(
  delegateAddress,
  true
);
await approvalTx.wait();
```

{% hint style="info" %}
Users can mass-revoke delegates across Curvance contracts by calling `centralRegistry.incrementApprovalIndex()`. See [Integration Cookbook ](/app/developer-docs/quick-start-guides/integration-cookbook)for the full delegation model.
{% endhint %}

***


# Deleveraging

### Understanding Deleveraging

Deleveraging reduces leverage inside one isolated market by withdrawing cToken underlying through a PositionManager, converting the collateral-side asset into the debt asset, repaying the account's debt, and returning eligible leftovers to the owner.

This page covers two common flows:

* **Partial deleverage:** reduce debt and collateral while keeping the position active.
* **Full unwind:** repay the target debt position and remove as much of the selected collateral position as possible.

This page assumes you already read Leverage, which covers `DeleverageAction`, `SwapperLib.Swap`, swap calldata rules, `auxData`, PositionManager fees, and position-health reads.

***

### 1. Preparing for Deleveraging

Before building calldata, identify the exact market and manager path:

1. Understand your current position. Check posted collateral shares with `cToken.collateralPosted(account)`, convert shares to underlying with `previewRedeem` when sizing a full unwind, read fresh debt with `debtBalanceUpdated.staticCall(account)`, and inspect position health with `ProtocolReader.getPositionHealth(...)`.<br>

   For full-unwind sizing, `ProtocolReader.getLeverageSnapshot(account, cToken, borrowableCToken, bufferTime)` can return projected `debtTokenBalance` using a `bufferTime`. Treat `oracleError` as a stop condition.
2. Confirm both cTokens are listed in that market with `await marketManager.isListed(cToken)`.
3. Read fresh debt with `await debtCToken.debtBalanceUpdated.staticCall(userAddress)`, or use `ProtocolReader.getLeverageSnapshot(...)` with a `bufferTime` for projected full-unwind sizing.
4. Remember that `collateralAssets` is an underlying-asset amount for `action.cToken`, not cToken shares. The cToken converts that amount into shares during `withdrawByPositionManager`.
5. Set three slippage controls: the aggregator route's own min-out, each `swapAction.slippage` for `SwapperLib._swapSafe`, and the top-level PositionManager `slippage` checked by `checkSlippage`.
6. Compute `collateralAssets` in `cToken` underlying units, not shares.

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

const MARKET_MANAGER = "0x...";
const POSITION_MANAGER = "0x...";
const COLLATERAL_CTOKEN = "0x...";
const DEBT_CTOKEN = "0x...";
const APPROVED_SWAP_TARGET = "0x...";
const PROTOCOL_READER = "0x...";

const COLLATERAL_DECIMALS = 18;
const DEBT_DECIMALS = 6;
const BPS = 10_000n;
const ONE_PERCENT_WAD = ethers.parseUnits("0.01", 18);

const MARKET_MANAGER_ABI = [
  "function isPositionManager(address positionManager) view returns (bool)",
  "function isListed(address cToken) view returns (bool)",
  "function redeemPaused() view returns (uint8)"
];

const POSITION_MANAGER_ABI = [
  "function deleverage((address cToken,uint256 collateralAssets,address borrowableCToken,uint256 repayAssets,(address inputToken,uint256 inputAmount,address outputToken,address target,uint256 slippage,bytes call)[] swapActions,bytes auxData) action,uint256 slippage)",
  "function deleverageFor((address cToken,uint256 collateralAssets,address borrowableCToken,uint256 repayAssets,(address inputToken,uint256 inputAmount,address outputToken,address target,uint256 slippage,bytes call)[] swapActions,bytes auxData) action,address account,uint256 slippage)",
  "function centralRegistry() view returns (address)",
  "function setDelegateApproval(address delegate,bool isApproved)"
];

const CTOKEN_ABI = [
  "function asset() view returns (address)",
  "function balanceOf(address account) view returns (uint256)",
  "function collateralPosted(address account) view returns (uint256)",
  "function previewRedeem(uint256 shares) view returns (uint256)",
  "function previewWithdraw(uint256 assets) view returns (uint256)"
];

const DEBT_CTOKEN_ABI = [
  ...CTOKEN_ABI,
  "function debtBalance(address account) view returns (uint256)",
  "function debtBalanceUpdated(address account) returns (uint256)"
];

const CENTRAL_REGISTRY_ABI = [
  "function protocolLeverageFee() view returns (uint256)"
];

const PROTOCOL_READER_ABI = [
  "function getPositionHealth(address mm,address account,address cToken,address borrowableCToken,bool isDeposit,uint256 collateralAssets,bool isRepayment,uint256 debtAssets,uint256 bufferTime) view returns (uint256,bool)",
  "function getLeverageSnapshot(address account,address cToken,address borrowableCToken,uint256 bufferTime) view returns (uint256 collateralUsd,uint256 debtUsd,uint256 collateralAssetPrice,uint256 sharePrice,uint256 debtAssetPrice,uint256 debtTokenBalance,bool oracleError)"
];

const marketManager = new ethers.Contract(
  MARKET_MANAGER,
  MARKET_MANAGER_ABI,
  provider
);
const positionManager = new ethers.Contract(
  POSITION_MANAGER,
  POSITION_MANAGER_ABI,
  signer
);
const collateralCToken = new ethers.Contract(
  COLLATERAL_CTOKEN,
  CTOKEN_ABI,
  provider
);
const debtCToken = new ethers.Contract(
  DEBT_CTOKEN,
  DEBT_CTOKEN_ABI,
  provider
);
const protocolReader = new ethers.Contract(
  PROTOCOL_READER,
  PROTOCOL_READER_ABI,
  provider
);

const userAddress = await signer.getAddress();
```

Run basic prechecks before quoting the route:

```js
const [
  isPositionManager,
  collateralListed,
  debtListed,
  redeemPaused,
  currentDebt
] = await Promise.all([
  marketManager.isPositionManager(POSITION_MANAGER),
  marketManager.isListed(COLLATERAL_CTOKEN),
  marketManager.isListed(DEBT_CTOKEN),
  marketManager.redeemPaused(),
  debtCToken.debtBalanceUpdated.staticCall(userAddress)
]);

if (!isPositionManager) throw new Error("PositionManager is not enabled");
if (!collateralListed) throw new Error("Collateral cToken is not listed");
if (!debtListed) throw new Error("Debt cToken is not listed");
if (redeemPaused === 2n) throw new Error("Redemption is paused for this market");
if (currentDebt === 0n) throw new Error("No debt to repay on this cToken");
```

### 2. Understand `repayAssets`

`DeleverageAction.repayAssets` is a minimum debt-asset balance floor after the deleverage swap path. It is not always the exact final repayment amount.

**The PositionManager flow is:**

1. Withdraw `action.collateralAssets` of `action.cToken` underlying from the owner through `withdrawByPositionManager`.
2. Apply `centralRegistry.protocolLeverageFee()` to the redeemed collateral asset before the manager-specific swap or exit logic.
3. Execute the manager-specific conversion from collateral-side asset into debt asset.
4. Require the PositionManager's debt-asset balance to be at least `action.repayAssets`.
5. Read fresh debt with `borrowableCToken.debtBalanceUpdated(owner)`.
6. Repay `min(assetsHeld, currentDebt)`.

`repayAssets = 0` reverts with `BasePositionManager__InvalidAmount()`. If the swap path produces less debt asset than `repayAssets`, the action reverts with `BasePositionManager__InsufficientAssetsForRepayment()`.

{% hint style="info" %}
If you over-quote a full unwind, the PositionManager caps the repayment at the current debt and returns remaining debt asset to the owner. The floor can still cause an avoidable revert if it is higher than the amount the route actually returns, so set it from a fresh quote plus a deliberate buffer.
{% endhint %}

***

### 3. Constructing the `DeleverageAction` struct

In `SimplePositionManager`, deleverage requires exactly one swap when the collateral asset and debt asset differ. The swap must be:

* `inputToken = action.cToken.asset()`.
* `outputToken = action.borrowableCToken.asset()`.
* `inputAmount = action.collateralAssets` after the PositionManager fee.
* `target` set to an approved swap target with a Central Registry calldata checker.
* `call` encoded so the swap output recipient is the PositionManager.

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

async function netAfterPositionManagerFee(positionManagerContract, grossAmount) {
  const centralRegistryAddress = await positionManagerContract.centralRegistry();
  const centralRegistry = new ethers.Contract(
    centralRegistryAddress,
    CENTRAL_REGISTRY_ABI,
    provider
  );

  const feeBps = await centralRegistry.protocolLeverageFee();
  const fee = mulDivUp(grossAmount, feeBps, BPS);
  if (fee >= grossAmount) throw new Error("PositionManager fee exceeds amount");
  return grossAmount - fee;
}
```

```js
const collateralAssets = ethers.parseUnits("300", COLLATERAL_DECIMALS);
const netCollateralToSwap = await netAfterPositionManagerFee(
  positionManager,
  collateralAssets
);
const minDebtOut = ethers.parseUnits("200", DEBT_DECIMALS);

const [collateralAsset, debtAsset] = await Promise.all([
  collateralCToken.asset(),
  debtCToken.asset()
]);

const netCollateralToSwap = await netAfterPositionManagerFee(
  positionManager,
  collateralAssets
);

// Aggregator calldata must send output to POSITION_MANAGER.
const swapCalldata = "0x...";

const swapActions = [
  {
    inputToken: collateralAsset,
    inputAmount: netCollateralToSwap,
    outputToken: debtAsset,
    target: APPROVED_SWAP_TARGET,
    slippage: ethers.parseUnits("0.01", 18),
    call: swapCalldata
  }
];

const deleverageAction = {
  cToken: COLLATERAL_CTOKEN,
  collateralAssets,
  borrowableCToken: DEBT_CTOKEN,
  repayAssets: minDebtOut,
  swapActions,
  auxData: "0x"
};
```

The supplied draft used `collateralAssets = 300` and `swapActions[0].inputAmount = 250` without tying the difference to the PositionManager fee. For the simple path, that mismatch reverts unless the post-fee amount is exactly `250`.

### 4. Simulate Position Health

Use `ProtocolReader.getPositionHealth(...)` to inspect the current account or simulate the collateral and debt changes from a deleverage.

```js
const [positionHealth, oracleError] =
  await protocolReader.getPositionHealth(
    MARKET_MANAGER,
    userAddress,
    COLLATERAL_CTOKEN,
    DEBT_CTOKEN,
    false,
    collateralAssets,
    true,
    minDebtOut,
    0n
  );

if (oracleError) throw new Error("Oracle error while simulating position");
if (positionHealth < ethers.parseUnits("1", 18)) {
  throw new Error("Projected position is below soft liquidation health");
}
```

{% hint style="info" %}
For full-unwind sizing, `getLeverageSnapshot(account, cToken, borrowableCToken, bufferTime)` returns a projected `debtTokenBalance`. Use a small `bufferTime` when you want to account for expected interest accrual before execution.
{% endhint %}

```js
const BUFFER_TIME_SECONDS = 120n;

const leverageSnapshot = await protocolReader.getLeverageSnapshot(
  userAddress,
  COLLATERAL_CTOKEN,
  DEBT_CTOKEN,
  BUFFER_TIME_SECONDS
);

const projectedDebt = leverageSnapshot.debtTokenBalance;
if (leverageSnapshot.oracleError) {
  throw new Error("Oracle error while sizing full deleverage");
}
```

***

### Executing the Deleverage Operation

```js
// Whole-action slippage tolerance, WAD-scaled. 1e16 = 1%.
const slippage = ethers.parseUnits('0.01', 18);

const tx = await positionManager.deleverage(
  deleverageAction,
  slippage,
  { gasLimit: 2_000_000n }  // optional overrides, not a positional arg
);

const receipt = await tx.wait();
console.log(`Position deleveraged. Tx hash: ${receipt.hash}`);
```

***

### Full vs Partial Deleverage

Both flows use the same `deleverage(action, slippage)` entrypoint; the difference is in `collateralAssets` and `repayAssets`:

* **Partial:** set `collateralAssets` to a fraction of your posted collateral and `repayAssets` to your minimum expected swap output. The debt position remains active afterward.
* **Full:** set `collateralAssets` to your full posted collateral and `repayAssets` to at least your current debt. After the swaps, the PositionManager caps the actual repay at `min(assetsHeld, currentDebt)`, so any surplus debt-asset is refunded. If the swap under-delivers, the action reverts entirely, leaving your position intact.

{% hint style="info" %}
**Tip for full unwinds:** because `repayAssets = min(assetsHeld, currentDebt)` on-chain, you can pass a slightly-more-than-debt `repayAssets` as the floor without worrying about "overpaying": the protocol won't accept a payment larger than the debt and will refund any surplus. Use `await cDebt.debtBalanceUpdated.staticCall(userAddress)` to get the fresh current debt for this calc, then add a small buffer (1 to 2%) to account for interest accruing during your tx's mempool dwell time.
{% endhint %}

***

### Important Considerations

#### Swap data configuration

* `swapActions` is an array, but multi-hop support depends on the specific PositionManager.
* Each entry must set its aggregator calldata `recipient` to the PositionManager so the output lands where the next step (or the repayment) can consume it.
* Per-swap `slippage` is separate from the whole-action `slippage` passed to `deleverage(...)`. The former bounds individual hops; the latter enforces overall position-value preservation via the `checkSlippage` modifier.

#### Amount calculations

* `collateralAssets` is expressed in the **underlying decimals**, not cToken share decimals. Internally the cToken converts to shares on your behalf.
* For a full-unwind flow, read fresh debt with `debtBalanceUpdated.staticCall` (see the Integration Cookbook: Stale vs Fresh Reads), then buffer for interest that will accrue during the transaction's mempool time.
* Aggregator routes change continuously; fetch fresh swap calldata close to broadcast time.

#### Leftovers are refunded to the user

The base deleverage callback sweeps remaining debt asset, remaining collateral asset, and balances of each swapActions\[i].outputToken back to the owner. Simple over-delivery in the debt asset is therefore refunded after repayment is capped at current debt.

Do not treat the PositionManager as user-specific custody. PositionManagers are router-style contracts, and arbitrary stranded balances outside the swept tokens are not a user custody guarantee.

#### Delegated deleverage

For platforms deleveraging on behalf of users, call `deleverageFor(DeleverageAction action, address account, uint256 slippage)`.

The account must approve the delegate on the PositionManager contract by calling `setDelegateApproval(delegate, true)` on that PositionManager. This is PositionManager delegation, not cToken delegation. cToken delegation powers cToken-specific `*For` functions such as `depositAsCollateralFor`, `postCollateralFor`, `redeemFor`, and `removeCollateralFor`.

#### Hold-period interaction

Posting collateral or borrowing starts or resets the 20-minute `MIN_HOLD_PERIOD` for the account. Deleveraging during that window can revert.

In the deleverage path, `repayFor` calls `MarketManager.canRepayWithReview(...)`, which checks the owner's hold period. The final redemption review also checks transfer and redeem eligibility through the `MarketManager`. Wait for the cooldown to clear before retrying a deleverage that follows a recent collateral post or borrow.

#### Protocol-specific features

* Some markets require populated `auxData` (Pendle PT, for instance). See the auxiliary-swap example in the Leveraging intro for the Pendle shape.
* For straightforward markets (USDC / WETH / WBTC), `auxData = '0x'`.

***


# Plugin Integration

This guide walks you through integrating your project with Curvance's plugin architecture, allowing your application to interact with Curvance's smart contracts on behalf of users.

For an in-depth exploration of the Plugin and Delegation system architecture, see [Plugin & Delegation System](/app/developer-docs/plugin-and-delegation-system).

***

### Understanding Plugin Delegation

Curvance's plugin system enables third-party applications to perform actions on behalf of users through a per-contract delegation mechanism. This powers:

* Automated portfolio management.
* Advanced trading strategies.
* Cross-chain operations.
* Reward auto-compounding.
* Sequential action chaining.

#### Where delegation state lives

* **Each cToken** inherits `PluginDelegable` through `BaseCToken`. Calling `setDelegateApproval(delegate, true)` on a cToken authorizes that delegate only for delegation-gated methods on that cToken, such as `borrowFor`, `redeemFor`, `redeemCollateralFor`, `depositAsCollateralFor`, `postCollateralFor`, and `removeCollateralFor`.
* **Each PositionManager** inherits `PluginDelegable` through `BasePositionManager`. Calling `setDelegateApproval(delegate, true)` on that PositionManager authorizes `leverageFor` and `deleverageFor` on that PositionManager only.\
  \
  `repayFor` is different: it is not delegation-gated. The caller supplies the repayment assets and repays debt for `owner`.
* **The CentralRegistry**, through `ActionRegistry`, stores the user's global approval index and delegation-disable state. Incrementing the user's approval index with `incrementApprovalIndex()` invalidates existing per-contract delegation slots across Curvance contracts, because `isDelegate` reads the current approval index.

**Implication for integrators:** if your platform needs to act on multiple cTokens, or trigger both a PositionManager action and a cToken action, the user must delegate on each relevant `PluginDelegable` contract separately.

***

### Enabling Delegation with `setDelegateApproval()`

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

const POSITION_MANAGER_ADDRESS = "0x...";
const PLATFORM_DELEGATE_ADDRESS = "0x...";

const PLUGIN_DELEGABLE_ABI = [
  "function setDelegateApproval(address delegate,bool isApproved)",
  "function checkNewDelegationDisabled(address user) view returns (bool)",
  "error PluginDelegable__DelegatingDisabled()",
  "error PluginDelegable_InvalidParameter()"
];

const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const userAddress = await signer.getAddress();

const positionManager = new ethers.Contract(
  POSITION_MANAGER_ADDRESS,
  PLUGIN_DELEGABLE_ABI,
  signer
);

async function approveDelegate() {
  if (await positionManager.checkNewDelegationDisabled(userAddress)) {
    throw new Error("Delegation changes are disabled or still in cooldown.");
  }

  const tx = await positionManager.setDelegateApproval(
    PLATFORM_DELEGATE_ADDRESS,
    true
  );

  const receipt = await tx.wait();
  return receipt;
}
```

**Call `setDelegateApproval` on every contract your platform needs to control:**

* The PositionManager for `leverageFor` and `deleverageFor`.
* Each cToken for delegation-gated cToken actions: `borrowFor`, `redeemFor`, `redeemCollateralFor`, `depositAsCollateralFor`, `postCollateralFor`, and `removeCollateralFor`.

A single approval is not global. `repayFor` is intentionally outside this list: any caller can repay debt for `owner` with assets the caller supplies, subject to the repay path's own market checks.

**Blocked edge cases:**

* `setDelegateApproval(msg.sender, _)` reverts with `PluginDelegable_InvalidParameter()`. A user cannot delegate to themselves.
* `setDelegateApproval(delegate, isApproved)` checks `checkNewDelegationDisabled(msg.sender)` for both approvals and per-contract revocations. That view returns true when delegation is disabled or when the re-enable cooldown has not expired. If it returns true, `setDelegateApproval` reverts with `PluginDelegable__DelegatingDisabled()`.

#### Error handling (ethers v6 pattern)

```js
async function approveDelegateSafe() {
  try {
    return await approveDelegate();
  } catch (error) {
    const errData = error.data ?? error.info?.error?.data;
    if (errData) {
      try {
        const decoded = positionManager.interface.parseError(errData);
        if (decoded.name === "PluginDelegable__DelegatingDisabled") {
          console.error("Delegation is disabled for this account");
        } else if (decoded.name === "PluginDelegable_InvalidParameter") {
          console.error("Cannot delegate to yourself");
        } else {
          console.error(`Revert: ${decoded.name}`, decoded.args);
        }
      } catch (_) {
        console.error("Unrecognized revert data:", errData);
      }
    } else {
      console.error("Non-revert error:", error.message);
    }
    throw error;
  }
}
```

***

### Verifying Delegation Status with `isDelegate()`

```js
const { ethers } = require("ethers");

const { YourProject_Address }            = require("./config/addresses");
const { CToken_PositionManager_Address } = require("./config/addresses");
const { CToken_PositionManager_ABI }     = require("./abis/positionManagerAbi");

// isDelegate is a view function; a provider is sufficient (no signer needed)
const provider        = new ethers.JsonRpcProvider("YOUR_RPC_URL");
const positionManager = new ethers.Contract(
  CToken_PositionManager_Address,
  CToken_PositionManager_ABI,
  provider
);

async function checkDelegate(userAddress) {
  // isDelegate(user, delegate) - both are addresses
  const approved = await positionManager.isDelegate(
    userAddress,
    YourProject_Address
  );
  console.log(`Platform is an approved delegate of ${userAddress}: ${approved}`);
  return approved;
}
```

{% hint style="warning" %}
**Important:** `isDelegate` reads `_isDelegate[user][userApprovalIndex(user)][delegate]`. If the user has mass-revoked (via `incrementApprovalIndex` on the CentralRegistry), **every** prior delegation immediately returns `false` without changing any per-contract state. Your integration should treat `isDelegate` as the source of truth before attempting a delegated action.
{% endhint %}

***

### Revoking Delegation

Users have two revocation paths.

#### Per-contract revocation

Call `setDelegateApproval(platformAddress, false)` on the specific cToken or PositionManager. This affects only that contract's delegation mapping at the user's current approval index. Because it uses `setDelegateApproval`, it still reverts while delegation changes are disabled or still in cooldown.

```js
const tx = await positionManager.setDelegateApproval(
  PLATFORM_DELEGATE_ADDRESS,
  false
);
await tx.wait();
```

#### Mass revocation (all contracts at once)

Call `incrementApprovalIndex()` on the CentralRegistry. This increments the caller's approval index, which invalidates previous delegation mappings across Curvance contracts without touching each cToken or PositionManager.

```js
const CENTRAL_REGISTRY_ADDRESS = "0x...";
const CENTRAL_REGISTRY_ABI = [
  "function incrementApprovalIndex()"
];

const centralRegistry = new ethers.Contract(
  CENTRAL_REGISTRY_ADDRESS,
  CENTRAL_REGISTRY_ABI,
  signer
);

const tx = await centralRegistry.incrementApprovalIndex();
await tx.wait();
```

Surface mass revocation prominently, especially in UIs that list many delegated platforms.

***

### Verification and Security Best Practices

* **Verify delegation before every delegation-gated action.** Call `isDelegate(user, platform)` on the specific cToken or PositionManager before calling a delegation-gated `*For` method. `repayFor` is not delegation-gated, but still runs the repay path's market checks.
* **Do not rely only on per-contract event caches.** Mass revocation emits `ApprovalIndexIncremented` from the CentralRegistry, not `DelegateApproval` from each cToken or PositionManager, so a cache built only from per-contract events can go stale.
* **Surface revocation options clearly.** Users should be able to revoke a specific platform with `setDelegateApproval(platform, false)` when delegation changes are enabled, or revoke everything with `incrementApprovalIndex()`.
* **Minimize scope.** Only request delegation on the contracts you actually need. Delegation on one cToken does not imply delegation on another cToken or on a PositionManager.
* **Transparent permissions.** Clearly explain which contracts the user is authorizing and which delegation-gated actions your platform will perform.
* **Watch for `checkNewDelegationDisabled`.** If it returns true, `setDelegateApproval` reverts for approvals and per-contract revocations. Detect this upfront with `checkNewDelegationDisabled(userAddress)` and provide a helpful error.
* **Use PositionManagers for bundled leverage flows.** `leverageFor` and `deleverageFor` already perform the borrow-swap-deposit and redeem-swap-repay loops through the PositionManager flow.

***

### Next Steps

After implementing the delegation setup, your application should:

1. Verify each delegation was successful using `isDelegate` on every relevant contract.
2. Store the user's delegation status in your application state, **but re-verify before every delegated action** (mass-revocation is silent).
3. Implement the specific delegation-gated actions your application needs: `borrowFor`, `redeemFor`, `redeemCollateralFor`, `leverageFor`, `deleverageFor`, `depositAsCollateralFor`, `postCollateralFor`, and `removeCollateralFor`. Treat `repayFor` as a non-delegated repay path where the caller supplies assets to repay debt for `owner`.
4. Provide users with both per-contract and global revocation flows.

For the full list of delegable actions and their signatures, see List of Delegable Actions.

For technical support or to get your plugin featured in Curvance's ecosystem, contact the developer relations team.

***


# List of Delegable Actions

Once a user has approved your contract as a delegate on a Curvance contract via `setDelegateApproval`, your application can perform that contract's delegated actions on their behalf. This page enumerates the plugin-delegated entrypoints that call `_checkDelegate`, plus the permissionless or PositionManager-only `*For` functions that are easy to confuse with plugin delegation.

***

### How the delegation check works

Not every `*For` function is plugin-delegated. The plugin-delegated entrypoints are the paths that call `_checkDelegate(userOrOwner, msg.sender)`, and the checked parameter name varies by contract: `receiver`, `owner`, `account`, `recipient`, or `user`. If the caller is not approved for that account on the contract being called, `_checkDelegate` reverts with `PluginDelegable__Unauthorized()`.

Delegation approval is stored on each contract that inherits `PluginDelegable`. Approving a delegate on one cToken does not approve it on another cToken or on a PositionManager. The current approval index comes from the Central Registry, so `centralRegistry.incrementApprovalIndex()` mass-revokes prior delegate approvals across `PluginDelegable` contracts.

***

### Delegable actions on each cToken

The cToken-level delegated actions below are implemented in `BaseCToken`, which inherits `PluginDelegable`; they are available on cToken implementations that inherit `BaseCToken`. Debt actions are implemented in `BorrowableCToken`. Do not infer borrowability from a market name or from "collateral" versus "debt" wording. Check `marketManager.debtCaps(cToken)`: zero means borrow attempts revert, while a nonzero cap means the market can carry debt up to that cap.

#### Collateral management

| Function                 | Signature                                           | Source               |
| ------------------------ | --------------------------------------------------- | -------------------- |
| `depositAsCollateralFor` | `(uint256 assets, address receiver)`                | `BaseCToken.sol:262` |
| `postCollateralFor`      | `(uint256 shares, address owner)`                   | `BaseCToken.sol:338` |
| `removeCollateralFor`    | `(uint256 shares, address owner)`                   | `BaseCToken.sol:363` |
| `redeemCollateralFor`    | `(uint256 shares, address receiver, address owner)` | `BaseCToken.sol:313` |

* `depositAsCollateralFor`: pull the caller's underlying, mint shares to `receiver`, and post them as collateral for `receiver` in one call.
* `postCollateralFor`: mark existing shares owned by `owner` as collateral.
* `removeCollateralFor`: unpost collateral for `owner`, leaving the shares as plain lending liquidity.
* `redeemCollateralFor`: redeem `owner`'s posted collateral shares to underlying and send the assets to `receiver`. This path forces the redeemed shares to come out of `owner`'s posted collateral. `redeemFor` does not force collateral removal; it uses idle shares first and removes posted collateral only if the redeemed share amount exceeds the owner's idle share balance.

#### Share redemption

| Function    | Signature                                           | Source               |
| ----------- | --------------------------------------------------- | -------------------- |
| `redeemFor` | `(uint256 shares, address receiver, address owner)` | `BaseCToken.sol:595` |

`redeemFor` burns `owner`'s shares and sends the resulting underlying to `receiver`. Delegation substitutes for the ERC20 share allowance that plain `redeem` requires when `msg.sender != owner`.

**There is no `withdrawFor`.** If your integration needs to pull a specific amount of underlying on behalf of a user, compute shares with `previewWithdraw(assets)` first, then call `redeemFor(shares, receiver, owner)`.

#### Debt management (BorrowableCToken only)

| Function    | Signature                                           | Source                     | Delegation-gated?                                |
| ----------- | --------------------------------------------------- | -------------------------- | ------------------------------------------------ |
| `borrowFor` | `(uint256 assets, address receiver, address owner)` | `BorrowableCToken.sol:194` | Yes. Checks `_checkDelegate(owner, msg.sender)`. |
| `repayFor`  | `(uint256 assets, address owner)`                   | `BorrowableCToken.sol:292` | **No** (permissionless)                          |

* `borrowFor`: take on new debt for `owner` and deliver the borrowed underlying to `receiver`. It requires prior delegation on the borrowed cToken, and the borrow still must pass the market's borrow checks, including the debt cap and account liquidity checks.
* `repayFor`: pull underlying from `msg.sender` and apply it against `owner`'s debt. It does not check delegation, so another account can repay a borrower's debt, but the caller still needs underlying token allowance and balance. Repayment also checks that `owner` has an active debt position, that the minimum hold period has passed, and that any remaining partial debt does not fall below `MIN_LOAN_SIZE`.

***

### Delegable actions on each PositionManager

Every Curvance PositionManager inherits `BasePositionManager` (which in turn inherits `PluginDelegable`). The following leverage actions are delegation-gated on the PositionManager itself, not on the cTokens.

| Function        | Signature                                                      | Source                        |
| --------------- | -------------------------------------------------------------- | ----------------------------- |
| `leverageFor`   | `(LeverageAction action, address account, uint256 slippage)`   | `BasePositionManager.sol:249` |
| `deleverageFor` | `(DeleverageAction action, address account, uint256 slippage)` | `BasePositionManager.sol:314` |

* `leverageFor`: borrow against `account`'s market liquidity, swap the borrowed asset into the collateral asset, and deposit it back as collateral.
* `deleverageFor`: redeem `account`'s collateral, swap it into the debt asset, and repay their debt.

{% hint style="info" %}
**Heads up:** `leverageFor` and `deleverageFor` require delegation on the PositionManager itself. A pure bundled PositionManager leverage or deleverage call does not require the user to delegate on the underlying cTokens. Internally, `leverageFor` calls `borrowForPositionManager`, which is authorized by `marketManager.isPositionManager(msg.sender)`, not by cToken plugin delegation. `deleverageFor` calls `withdrawByPositionManager`, which is also PositionManager-only, and then uses `repayFor`, which is permissionless.

Separate cToken calls before or after the bundled PositionManager action require approval on the specific cToken being called. For example, a delegated `redeemFor` on the collateral cToken needs collateral cToken delegation, and a delegated `borrowFor` on the debt cToken needs debt cToken delegation.
{% endhint %}

***

### Common calling pattern

All `*For` functions that take an `owner` or `account` parameter require the caller to be a pre-approved delegate of that owner on the specific contract being called:

```js
const userAddress = await user.getAddress();
const delegateAddress = await platform.getAddress();

// Add approvals only on contracts your platform will call directly.
// These approvals must be sent by the user.
const collateralApprovalTx = await collateralCToken
  .connect(user)
  .setDelegateApproval(delegateAddress, true);
await collateralApprovalTx.wait();

const positionManagerApprovalTx = await positionManager
  .connect(user)
  .setDelegateApproval(delegateAddress, true);
await positionManagerApprovalTx.wait();

// The platform can now invoke delegated calls on those contracts.
const redeemTx = await collateralCToken
  .connect(platform)
  .redeemFor(shares, delegateAddress, userAddress);
await redeemTx.wait();

const leverageTx = await positionManager
  .connect(platform)
  .leverageFor(action, userAddress, slippage);
await leverageTx.wait();
```

See [Plugin Integration](/app/developer-docs/quick-start-guides/plugin-integration) for the setup flow, per-contract verification via `isDelegate`, and revocation (both per-contract and global via `incrementApprovalIndex`).

***


# Liquidations

## Quick Start: Liquidations

### Overview

Liquidations are the mechanism that keeps Curvance solvent. When a borrower's position becomes undercollateralized, third parties (liquidators) can repay part or all of the debt in exchange for seizing the borrower's collateral at a discount. Liquidators are compensated for the service; the protocol avoids bad debt accumulation.

***

### How Liquidations Work

#### Collateral requirements

Each collateral-capable cToken is configured with a collateral ratio plus two liquidation thresholds:

* **Soft requirement:** the adjusted collateral value where a position first becomes partially liquidatable.
* **Hard requirement:** the adjusted collateral value where standard protocol-derived liquidation can close the full debt position and may realize bad debt if posted collateral is insufficient.

Read the values for a given market with `marketManager.collConfig(cToken)`, which returns `(collRatio, collReqSoft, collReqHard)`. These values are BPS-denominated. `collReqSoft` and `collReqHard` are stored as `BPS + configuredPremium`, so integrations should read the returned values rather than reconstructing them from deployment inputs.

#### Health factor (`lFactor`)

| lFactor           | Meaning          | Behaviour                                                          |
| ----------------- | ---------------- | ------------------------------------------------------------------ |
| `0`               | Healthy          | Not liquidatable                                                   |
| `1` ... `WAD - 1` | Soft liquidation | Partial liquidation allowed; size and penalty scale with `lFactor` |
| `WAD` (`1e18`)    | Hard liquidation | Full position can be closed; can realize bad debt                  |

`lFactor` is computed as `mulDivUp(debt - cSoft, WAD, cHard - cSoft)` when the debt lands between the soft and hard thresholds (`LiquidityManagerIsolated.sol:575`). It interpolates linearly from "just crossed soft" to "at hard."

#### Dynamic close factor and liquidation incentive

* **Close factor:** when no auction-specific close factor is supplied, the protocol computes `closeFactor = closeFactorBase + (closeFactorCurve * lFactor / WAD)`. `closeFactorCurve` is stored as `BPS - closeFactorBase`, so a standard hard liquidation reaches a 100% close factor.
* **Liquidation incentive:** when no auction-specific incentive is supplied, the protocol computes `liqInc = liqIncBase + (liqIncCurve * lFactor / WAD)`. `liqIncBase` is stored as `BPS + baseIncentive`, and `liqIncCurve` is the distance from the base incentive to the hard-liquidation incentive.

Read the stored liquidation parameters with `marketManager.liquidationConfig(cToken)`. They are configured per cToken inside a market through `updateTokenConfig`; auction flows can temporarily supply a bounded incentive or close factor with `setTransientLiquidationConfig`, while passing zero uses protocol-derived values.

***

### When Positions Become Liquidatable

A position crosses into `lFactor > 0` when any of these hold:

* Collateral value falls relative to debt (collateral price drop).
* Debt asset value rises relative to collateral (debt price spike).
* Interest accrues on debt, pushing the position past the soft requirement.
* Any combination of the above.

***

### Two Types of Liquidations

#### Non-Auction Liquidations

Standard liquidations are permissionless when market-wide liquidations are not paused and the account has a repayable liquidation amount. Without an active auction unlock, the MarketManager uses the protocol-derived close factor and liquidation incentive from the current `lFactor`.

#### Auction-Enabled Liquidations

Auction-enabled liquidations apply only inside an authorized auction flow. The auction setup stores an unlocked collateral cToken plus optional transient incentive and close-factor values, then unlocks the market for the transaction. The MarketManager applies `AUCTION_BUFFER` only when both the market is unlocked and the requested collateral token matches the unlocked cToken; otherwise the attempted auction liquidation reverts.\
\
`AUCTION_BUFFER` discounts `cSoft` and `cHard`, so auction calculations can cross the soft threshold earlier or produce a higher `lFactor` than the non-auction path. The buffer is 9990 for correlated markets and 9950 for uncorrelated markets. Auction-specific incentive and close-factor values are bounded by each cToken's configured min/max values; zero means the protocol derives that value from `lFactor`.

***

### Liquidator Workflow

1. **Discover liquidatable accounts** by indexing position and debt activity, including `PositionUpdated`, `Borrow`, `Repay`, `CollateralUpdated`, and `Liquidated`. Account health can also change because oracle prices move or interest accrues, so event indexing should be paired with current reads.<br>

   For screening, use `ProtocolReader.liquidationValuesOf(marketManager, account, false)` for the standard path or `true` for the auction-buffered path. It returns `(cSoft, cHard, debt, lFactor, errorCodeHit)`. Treat that as a read-side screen, then simulate the liquidation entrypoint or `canLiquidate` before submitting a transaction.
2. **Validate via `canLiquidate`.** Get exact amounts, seized shares, and any bad-debt realization before committing to a tx.
3. **Execute** `liquidate()` (max amount) or `liquidateExact()` (caller-specified per-account amounts) on the debt cToken.
4. **Receive collateral.** Seized collateral shares are transferred directly to the liquidator's address.

#### Entrypoints

```solidity
// BorrowableCToken.sol:333 - liquidate as much as possible across all accounts
function liquidate(
    address[] calldata accounts,
    address collateralToken
) external;

// BorrowableCToken.sol:306 - liquidate caller-specified amounts per account
function liquidateExact(
    uint256[] calldata debtAmounts,
    address[] calldata accounts,
    address collateralToken
) external;
```

{% hint style="warning" %}
**Critical non-obvious requirement:** `accounts` **must be sorted in strictly ascending address order with no duplicates**. `canLiquidate` reverts with `MarketManager__Unauthorized()` when `priorAccount >= cachedAccount`, so sorting and deduping must happen client-side.

Self-liquidation is also blocked. If `liquidator == accounts[i]`, `canLiquidate` reverts with `MarketManager__Unauthorized()`.
{% endhint %}

**Self-liquidation is blocked.** `liquidator == accounts[i]` reverts (`:598`). You cannot liquidate yourself.

#### Pre-liquidation check via `canLiquidate`

`canLiquidate` at `MarketManagerIsolated.sol:569` is the primary off-chain helper. It's not a `view` function (it reads mutable state during price accrual) but can be simulated via `staticCall`:

```js
const debtAmounts = Array(accounts.length).fill(0n);

const [result, adjustedDebtAmounts] =
  await marketManager.canLiquidate.staticCall(
    debtAmounts,
    liquidatorAddress,
    accounts,
    {
      collateralToken: COLLATERAL_CTOKEN,
      debtToken: DEBT_CTOKEN,
      numAccounts: BigInt(accounts.length),
      liquidateExact: false,
      liquidatedShares: 0n,
      debtRepaid: 0n,
      badDebt: 0n,
    }
  );
```

For maximum liquidation through `canLiquidate`, pass a zero-filled `debtAmounts` array with the same length as `accounts` and set `liquidateExact: false`. For exact liquidation, pass one desired debt amount per account and set `liquidateExact: true`. The helper returns a tuple, not an object with `result` and `remaining` fields.

If you want the closest end-to-end simulation of the public liquidation path, simulate `debtCToken.liquidate.staticCall(accounts, collateralToken)` or `debtCToken.liquidateExact.staticCall(debtAmounts, accounts, collateralToken)`. Those paths run the debt cToken's pre-liquidation accrual and then call `marketManager.canLiquidate`.

`result` contains `liquidatedShares[]` (collateral shares you'd receive per account), total `debtRepaid`, and any `badDebtRealized`. Use this to compute expected profit after gas.

***

### Key Considerations

* **Account discovery:** index `PositionUpdated`, `Borrow`, `CollateralUpdated`, and `Repay` events. Cross-reference with a fresh `lFactor` read via `liquidationValuesOf(account, auctionBuffer=0)` before committing to a liquidation tx.
* **Profitability:** `liquidatedShares` are cToken shares, so convert seized shares through the current exchange rate and collateral price before comparing against `debtRepaid`, swap costs, flash-loan costs, and gas. In standard protocol-derived liquidations, the incentive increases with `lFactor`, but profit is not guaranteed. For `liquidateExact`, the call reverts if the requested debt amount exceeds the computed max debt or if the resulting seized shares exceed the borrower's posted collateral.
* **Smart contracts over scripts:** a Solidity liquidator contract can atomically check profitability and revert if conditions change mid-tx. Off-chain scripts can't guarantee atomicity; a competing liquidator's tx landing first can either empty the position or change pricing state.
* F**lash loans:** a liquidator contract can use `flashLoan` on the debt `BorrowableCToken` to source the repayment asset. The callback is `onFlashLoan(assets, assetsReturned, data)`. Inside the callback, execute the liquidation, convert seized collateral as needed, and end with enough debt asset plus allowance for the cToken to pull `assetsReturned`. See Flash Loans.
* **Competition:** popular liquidation opportunities are MEV-competitive. Optimize for speed (direct node RPC, private mempools), gas efficiency, and atomic tx design.
* **Oracle gating:** liquidation pricing asks the Oracle Manager for the collateral cToken share price and the debt cToken underlying price with `BAD_SOURCE` as the error-code breakpoint. If either side reaches that breakpoint, the oracle call reverts and the liquidation does not proceed. Lower-severity `CAUTION` responses do not block this path.

***


# Non-Auction Liquidations

### Step 1: Check if a Position is Liquidatable

Use `getPositionHealth` as a broad risk screen, but use `ProtocolReader.liquidationValuesOf(marketManager, account, false)` for non-auction liquidation monitoring. It returns `(cSoft, cHard, debt, lFactor, errorCodeHit)`, where `lFactor == 0` means no liquidation, values from `1` to `WAD - 1` mean soft liquidation, and `WAD` means hard liquidation. Simulate `liquidate` or `liquidateExact` before submitting, because `canLiquidate` applies the selected debt token, selected collateral token, close factor, and current liquidation pause checks.

### Step 2: Executing Liquidations

Two entrypoints exist on every `BorrowableCToken`, depending on whether you want to liquidate the maximum allowed or an exact amount.

#### `liquidate()` – maximum liquidation

Use when you want the protocol to liquidate as much as it legally can given the current `lFactor` and close factor. Typical for "just close this bad position."

```solidity
function liquidate(
    address[] calldata accounts,
    address collateralToken
) external nonReentrant;
```

#### `liquidateExact()` – caller-specified amount per account

Use when you have an exact debt repayment in mind (for example because you're flash-loaning a fixed amount and want to stay within budget).

```solidity
function liquidateExact(
    uint256[] calldata debtAmounts,
    address[] calldata accounts,
    address collateralToken
) external nonReentrant;
```

**Required in both:** the `accounts[]` array must be sorted strictly ascending by address, with no duplicates. The internal check reverts with `__Unauthorized` if `priorAccount >= cachedAccount` (`MarketManagerIsolated.sol:605`). Sorting must happen client-side before you submit the tx.

**In `liquidateExact`:** `debtAmounts.length` must equal `accounts.length`. The call reverts with `__InvalidParameter` otherwise (`BorrowableCToken.sol:312-313`).

**In both:** you cannot liquidate yourself. `liquidator == accounts[i]` reverts with `__Unauthorized` (`MarketManagerIsolated.sol:598`).

***

### Token Flows

* **What the liquidator pays:** the debt cToken's underlying asset, pulled from the liquidator with `safeTransferFrom(asset(), liquidator, address(this), result.debtRepaid)`. `badDebtRealized` is accounted against market debt and lender assets; it is not an extra token transfer from the liquidator.
* **What the liquidator receives:** cToken shares of `collateralToken`, reported as one `liquidatedShares` value per account. The collateral cToken's `seize` path removes posted collateral shares from each borrower and transfers those shares to the liquidator. To realize underlying collateral, redeem the received shares with `redeem(shares, liquidator, liquidator)` on the collateral cToken.

***

### Pre-flight: computing `result` via `canLiquidate`

```js
// Accounts MUST be sorted ascending with no duplicates
const accounts = [
  "0x1111111111111111111111111111111111111111",
  "0x2222222222222222222222222222222222222222",
  "0x3333333333333333333333333333333333333333",
];

// Empty debtAmounts means "liquidate the maximum for each account"
const debtAmounts = new Array(accounts.length).fill(0n);

const liquidatorAddress = await signer.getAddress();

// Simulate to get liquidatedShares[], total debtRepaid, and any badDebt
const [result] = await marketManager.canLiquidate.staticCall(
  debtAmounts,
  liquidatorAddress,
  accounts,
  {
    collateralToken: COLLATERAL_CTOKEN,
    debtToken:       DEBT_CTOKEN,
    numAccounts:     BigInt(accounts.length),
    liquidateExact:  false,
    liquidatedShares: 0n,
    debtRepaid:      0n,
    badDebt:         0n,
  }
);

console.log(`Will repay: ${result.debtRepaid}`);
console.log(`Will receive (cToken shares): ${result.liquidatedShares}`);
if (result.badDebt > 0n) {
  console.warn(`Liquidation will realize ${result.badDebt} of bad debt`);
}
```

**Profitability check before submitting:**

```js
const totalLiquidatedShares = result.liquidatedShares.reduce(
  (sum, shares) => sum + shares,
  0n
);
const expectedCollateralAssets =
  await collateralCToken.previewRedeem(totalLiquidatedShares);

const gasEstimate =
  await debtCToken.liquidate.estimateGas(accounts, COLLATERAL_CTOKEN);
const feeData = await provider.getFeeData();
const gasPrice = feeData.gasPrice ?? feeData.maxFeePerGas;
if (gasPrice == null) {
  throw new Error("Provider did not return gas pricing");
}
const gasCostWei = gasEstimate * gasPrice;

// Convert `expectedCollateralAssets` and `gasCostWei` into the debt asset
// through your own configured quote path. Compare that value against
// `result.debtRepaid`, swap costs, flash-loan fees, and your profit threshold.
```

***

### Examples

#### Executing a maximum liquidation

```js
// Prepare sorted accounts (ascending, no duplicates)
const accounts = [
  "0x1111111111111111111111111111111111111111",
  "0x2222222222222222222222222222222222222222",
  "0x3333333333333333333333333333333333333333",
];

const liquidatorAddress = await signer.getAddress();

// 1. Simulate to determine the exact debt to repay
const [result] = await marketManager.canLiquidate.staticCall(
  new Array(accounts.length).fill(0n),
  liquidatorAddress,
  accounts,
  {
    collateralToken: COLLATERAL_CTOKEN,
    debtToken:       DEBT_CTOKEN,
    numAccounts:     BigInt(accounts.length),
    liquidateExact:  false,
    liquidatedShares: 0n,
    debtRepaid:      0n,
    badDebt:         0n,
  }
);

// 2. Instantiate the underlying debt token and approve if needed
const underlyingDebtAddress = await borrowableCUSDC.asset();
const underlyingDebt = new ethers.Contract(
  underlyingDebtAddress,
  ["function approve(address,uint256) returns (bool)",
   "function allowance(address,address) view returns (uint256)"],
  signer
);
const currentAllowance = await underlyingDebt.allowance(liquidatorAddress, DEBT_CTOKEN);
if (currentAllowance < result.debtRepaid) {
  const approveTx = await underlyingDebt.approve(DEBT_CTOKEN, result.debtRepaid);
  await approveTx.wait();
}

// 3. Execute batch liquidation
const liquidateTx = await borrowableCUSDC.liquidate(accounts, COLLATERAL_CTOKEN);
await liquidateTx.wait();

console.log("All three accounts liquidated in one transaction");
console.log(`Received ${result.liquidatedShares} cToken shares of COLLATERAL_CTOKEN`);
```

#### Executing an exact-amount liquidation

```js
// Liquidate exactly 500 units of the debt asset against a single borrower.
const accounts = ["0x1111111111111111111111111111111111111111"];
const debtAmounts = [ethers.parseUnits("500", 6)];

const liquidatorAddress = await signer.getAddress();

const [result] = await marketManager.canLiquidate.staticCall(
  debtAmounts,
  liquidatorAddress,
  accounts,
  {
    collateralToken: COLLATERAL_CTOKEN,
    debtToken: DEBT_CTOKEN,
    numAccounts: BigInt(accounts.length),
    liquidateExact: true,
    liquidatedShares: 0n,
    debtRepaid: 0n,
    badDebt: 0n,
  }
);

const underlyingDebtAddress = await debtCToken.asset();
const underlyingDebt = new ethers.Contract(
  underlyingDebtAddress,
  [
    "function approve(address,uint256) returns (bool)",
    "function allowance(address,address) view returns (uint256)",
  ],
  signer
);

const currentAllowance =
  await underlyingDebt.allowance(liquidatorAddress, DEBT_CTOKEN);
if (currentAllowance < result.debtRepaid) {
  const approveTx = await underlyingDebt.approve(DEBT_CTOKEN, result.debtRepaid);
  await approveTx.wait();
}

const liquidateTx = await debtCToken.liquidateExact(
  debtAmounts,
  accounts,
  COLLATERAL_CTOKEN
);
await liquidateTx.wait();

console.log(`Repaid ${result.debtRepaid} of debt asset`);
```

***

### Liquidation Tips

#### Finding liquidation opportunities

* **Monitor oracle price updates** for significant collateral price drops or debt asset price spikes.
* **Track accounts whose non-auction** `lFactor` is above zero and approaching `WAD`. Use `ProtocolReader.liquidationValuesOf(marketManager, account, false)` to rank candidates by liquidation severity, then simulate the selected collateral/debt pair before submitting. Do not reference `_liquidationValuesOf` in integration docs; it is an internal MarketManager helper.
* Focus on volatile collateral assets during market downturns; LSTs, for example, can exhibit sudden depegs that move large numbers of positions at once.
* Set up event listeners for `PositionUpdated`, `Borrow`, `Repay`, `CollateralUpdated`, and `Liquidated`, then rescore accounts with current reads. Prices and interest accrual can make an account liquidatable without a fresh user action event.

#### Using smart contracts vs scripts

Smart contracts are strongly recommended over off-chain scripts for executing liquidations:

* **Atomic profitability checks:** the contract can quote expected profit on-chain and revert the whole tx if unprofitable. Scripts can't guarantee this: pricing can shift between your off-chain quote and the on-chain execution.
* **Flash loan integration:** a liquidator contract can call `flashLoan` on the debt `BorrowableCToken` to receive the underlying debt asset. The cToken transfers `assets`, calls `onFlashLoan(assets, assetsReturned, data)` on `msg.sender`, then pulls `assetsReturned`. The fee is `flashFee(assets) = ceil(assets * 4 / 10_000)`, so the callback must finish with enough underlying debt asset and allowance for the cToken to pull repayment. See Flash Loans.
* **Composability:** you can chain the liquidation, collateral conversion, and a profit-threshold check inside a single contract call. After liquidation, the liquidator holds collateral cToken shares; redeem those shares to underlying before swapping unless your route explicitly accepts cTokens.

***


# Auction Enabled Liquidations

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2F9Th78uPzurHJLXy9rvlI%2Fimage.png?alt=media&amp;token=7140cac9-a7c3-49a2-b3ec-d923245ebe5a" alt=""><figcaption><p>More information coming soon!</p></figcaption></figure>


# Lending Protocol

## Market Design Principles

Curvance markets are built with a focus on risk management, liquidity efficiency, and economic security. The protocol employs a novel market architecture that enables both capital efficiency and strong risk management.

### Thesis-Driven Markets

Curvance creates thesis-driven micro-ecosystems. Each Market Manager focuses on a specific financial thesis:

* Interest-bearing stablecoins.
* Bluechip long market exposure.
* Volatile LP tokens for a particular DEX or perpetual platform.
* Other specialized asset categories.

This targeted approach allows for customized risk parameters appropriate for each asset class, rather than forcing disparate assets to share the same risk model.

### Systemic Risk Reduction

By segregating markets by thesis, Curvance minimizes the contagion risk between asset classes. A volatility event in one market doesn't propagate to unrelated markets, protecting the overall protocol health. Isolated Market Managers are designed to contain risk within their boundaries. If extreme market conditions impact one Isolated Market Manager, other Market Managers remain unaffected, ensuring protocol stability.

## Core Market Components

### Curvance Token System

**Curvance Tokens (cTokens)** are ERC4626 vault shares representing user deposits. Each cToken wraps exactly one underlying asset. Deposits increase the cToken's asset accounting and mint shares. Available liquidity remains in the cToken contract, while borrowed liquidity is tracked as `marketOutstandingDebt` and per-account debt, and the underlying is transferred to the borrower.

* **Lending liquidity:** holders of cToken shares are the suppliers. Interest accrual increases cToken asset accounting and can raise share value through the exchange rate. Realized bad debt reduces that asset accounting, so the exchange rate is not guaranteed to only increase. User share balances stay fixed unless they mint, burn, transfer, or have collateral seized.
* **Debt accounting for borrowers:** when a user borrows from the cToken, no cToken shares are minted to them. Instead, the cToken records their debt in an internal `_debtOf` mapping and delivers the underlying directly. Debt grows with interest and is denominated in the underlying asset.
* **Collateral:** an account can post its cToken shares as collateral to support borrowing. Collateral status is tracked per-account via `collateralPosted[account]`.

**Designating a cToken as borrowable in a given market** is governed by the token's configured `debtCap`, not by the `isBorrowable()` function alone. `isBorrowable()` distinguishes the contract type (`BorrowableCToken` vs. pure-lending `BaseCToken`), and is `true` for every live cToken in current deployments. Whether a specific cToken can actually be borrowed from inside a market is determined by `TokenConfig.debtCap > 0`. An asset with `debtCap = 0` cannot be borrowed in that market. It is collateral-only only when collateralization is also enabled through that market's collateral configuration.

This unified approach simplifies the token architecture while maintaining the flexibility for assets to serve different roles within the lending protocol.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FmCQFF8OccspKQ18ynRef%2Fimage.png?alt=media&amp;token=4be41e61-a20b-47fb-aa95-70aeaa2599f8" alt=""><figcaption></figcaption></figure>

### Market Manager

The Market Manager is the central contract that:

* Manages risk parameters for all tokens in its isolated market.
* Handles collateral posting and borrow authorization.
* Coordinates liquidation processes (dynamic close factor, dynamic liquidation incentive, auction gating).
* Runs position health checks for borrows and collateral-removing redeems, withdrawals, and transfers. Deposits and mints are checked for listing and mint pause status.
* Each `BorrowableCToken` is linked to a Dynamic Interest Rate Model at deployment and can update that link through `setIRM()`. The Market Manager stores market risk parameters and cap configuration, but it does not wire cTokens to IRMs.

### Position Lifecycle

1. **Deposit:** User deposits underlying assets and receives cTokens representing\
   their share of the pool.
2. **Collateral Posting:** User activates their cTokens as collateral to secure\
   borrowing capacity.
3. **Borrowing:** User borrows underlying assets from borrowable cToken markets\
   against their collateral.
4. **Repayment:** User repays borrowed assets plus accrued interest to reduce debt\
   obligations
5. **Withdrawal:** User redeems cTokens for underlying assets after ensuring\
   adequate collateralization or full debt repayment.

## Asset Management

### Collateral and Debt Management System

The asset management has several key components:

* **Collateral Caps**: Each cToken has a maximum amount of shares that can be posted as collateral, limiting exogenous risk exposure.
* **Debt Caps:** Each cToken has a maximum amount of underlying assets that can be borrowed, providing additional risk management for lending exposure.
* **Collateral Posting:** Assets are posted as shares, allowing caps to grow proportionally with any yield-generating strategies.&#x20;
* **Cap Management:** Both collateral and debt caps can be decreased even if current utilization is above the new cap, which prevents new risk while not forcing position unwinding.&#x20;
* **Dual-cap risk control:** the combination of collateral caps, which limit how many shares can be posted as collateral, and debt caps, which limit borrow-side exposure, provides risk management across both sides of the lending market. Collateral caps are not deposit caps; cTokens can hold deposits beyond the amount that can be posted as collateral.

### Dynamic Liquidation Engine (DLE)

The Dynamic Liquidation Engine enables more nuanced position management:

#### Liquidation State Machine:

1. **Healthy position:** collateral value exceeds required thresholds; `lFactor = 0`.
2. **Soft liquidation threshold:** when the collateral-to-debt ratio falls below the soft threshold, partial liquidations begin. `lFactor` interpolates upward from 0.
3. **Hard liquidation threshold:** when the ratio falls below the hard threshold, complete liquidation is permitted with maximum penalty. `lFactor = WAD` (1e18).
4. **Bad debt handling:** bad debt is not a separate `lFactor` threshold. It is realized during liquidation when the collateral shares needed to cover the debt plus liquidation compensation exceed the account's posted shares. The realized amount is reported as `badDebtRealized` and the debt cToken reduces `_totalAssets`, passing the loss to lenders through the exchange rate.

#### OEV Liquidation Auctions

Curvance features a next-generation liquidation auction system, OEV (Oracle Extractable Value) that maximizes MEV capture:

1. Auction permissions and market unlocks:
   1. Approved auction addresses can unlock a specific Market Manager for auction-based liquidations during the current transaction.
   2. Approved auction addresses can set transient liquidation configuration for a specific collateral cToken, including optional auction close factor and liquidation incentive values.
   3. If both the unlocked market and unlocked collateral match, the liquidation receives the auction priority buffer and any transient parameters. If an auction liquidation is misconfigured, it reverts.
2. Batch liquidation execution:
   1. `liquidate()` and `liquidateExact()` accept arrays of accounts, allowing multiple accounts to be processed in one transaction.
   2. The on-chain loops process accounts sequentially, not concurrently.
3. Off-chain auction coordination:
   1. Off-chain bidding and solver ordering happen outside the core lending contracts.
   2. The on-chain lending contracts enforce auction permissions, market and collateral unlocks, transient liquidation parameters, and liquidation accounting.

### Risk Modeling

Curvance enhances risk modeling through:

1. **Asset-Specific Risk Parameters:** Each asset has customized collateralization requirements.
2. **Three-Tier Liquidation System:** Soft, hard, and bad debt thresholds for graduated liquidation responses.
3. **Position-Health-Responsive Liquidations:** Close factor and liquidation incentive scale with `lFactor`, so deeper shortfall permits larger and more punitive liquidations.
4. **Bad Debt Socialization:** When liquidation realizes `badDebtRealized` because the account's posted collateral cannot cover the debt plus liquidation compensation, the debt cToken reduces `_totalAssets`, passing the loss to lenders through the exchange rate.

The overall architecture provides a powerful framework for managing diverse asset classes while maintaining protocol solvency and capital efficiency.


# Market Manager

## Overview

The Market Manager is a core component of the Curvance protocol, responsible for risk management and orchestrating interactions between various protocol modules. Market Managers are designed as thesis-driven micro-ecosystems, each focusing on specific asset types (e.g., interest-bearing stablecoins, bluechip assets, volatile LP tokens), which helps minimize systemic risk across the protocol.

### Architecture

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2F8xtMxzAuyzas8DFdesE4%2Fimage.png?alt=media&amp;token=794254e5-5ea3-4581-a87e-c05651a3e6e5" alt=""><figcaption></figcaption></figure>

* **Market Manager:** Orchestrates listing, pausing, liquidation logic (including auctions), collateral/debt caps, and permissions for a single-ecosystem market.
* **Liquidity Manager:** Computes account liquidity, status, and hypothetical actions, including the `MIN_LOAN_SIZE` check used by borrow and collateral-removing flows. The 20-minute hold period is started and enforced through the Market Manager's collateral, borrow, repay, transfer, redeem, and withdraw review paths.
* **Dynamic Interest Rate Model:** External IRM module that updates borrow/supply rates based on utilization and configured parameters.
* **Oracle Manager:** Provides the prices used by Market Manager risk checks. For live external docs, describe Curvance pricing as single-source per asset with heartbeat validation and price guards where configured, not as a dual-oracle or price-divergence system.
* **cTokens:** ERC4626-like market tokens. `collateralCaps(cToken)` limits how many shares can be posted as collateral, while `debtCaps(cToken)` limits how much of that cToken's underlying asset can be borrowed. A zero debt cap disables borrowing for that asset in the market, and a zero collateral cap blocks additional collateral posting. Collateral support also depends on the cToken's collateral configuration, including a nonzero `collRatio`.

#### Market Tokens

**cTokens (Curvance tokens):** One listed per asset in a market.

* **Collateralization:** Collateralization: `collateralCaps[cToken]` in shares; zero disables posting.
* **Borrowable:** `debtCaps[cToken]` in underlying assets; zero disables borrowing of this asset.

### Data Flow

#### Collateral Flow

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FwfNkLWaxaLO6OGymStyQ%2Fimage.png?alt=media&amp;token=ee588465-a932-4b3e-b0d0-4e98bfc80bb7" alt=""><figcaption></figcaption></figure>

1. Users deposit into a cToken to receive shares.
2. Users may post those cToken shares as collateral (subject to caps and pauses).
3. Posted collateral increases borrowing capacity, tracked in the Market Manager and cToken.

#### Liquidation (auction-aware) flow

1. The Market Manager supports auction-based liquidations using transient, per-tx config set by authorized auction callers.
2. Authorized callers set a temporary liquidation incentive and close factor; auctions receive an additional `AUCTION_BUFFER` priority on thresholds.
3. If no transient config is present, standard liquidations apply.

### Risk Management

#### Dynamic Liquidation Engine (DLE)

1. The Market Manager implements a three-tiered configuration per cToken:
   1. **Collateralization ratio (`collRatio`):** Base borrowing power while healthy.
   2. **Soft Liquidation Threshold (`collReqSoft`)**
      1. Initiates partial liquidations with base incentives.
      2. Minimal disruption to user positions.
   3. **Hard Liquidation Threshold (`collReqHard`)**
      1. Permits complete position liquidation.
      2. Maximum liquidation incentives within configured bounds.

#### Bad debt socialization

If a user's posted collateral cannot cover the debt plus liquidation compensation during liquidation, the shortfall is recognized as `badDebtRealized`. The Market Manager computes this amount during liquidation evaluation, and the debt cToken realizes the loss by reducing `_totalAssets`, passing the loss to lenders through the exchange rate.

#### Caps

* Collateral Caps: Per-cToken maximum posted collateral, measured in shares. Scales naturally with auto-compounding strategies.
* Debt Caps: Per-cToken maximum outstanding borrow, measured in underlying assets.
* Caps can be lowered without forcing immediate unwinds; posting/borrowing operations enforce caps at the time of action.

#### Position Cooldown (20 minutes)

* A 20-minute hold period mitigates flash-loan and short-term oracle manipulation. The cooldown starts (or resets) on:
  * Posting collateral
  * Borrowing

While an account's cooldown is active, `MarketManager__MinimumHoldPeriod` blocks:

* Repayment through `canRepayWithReview`.
* cToken transfers that route through `canTransfer`.
* Redeem and withdraw paths that call `canRedeemWithCollateralRemoval`, including collateral-removing redemptions and withdrawals.

The state transition looks like:

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FfEEsSIcR6PHsax72YlGA%2Fimage.png?alt=media&amp;token=6e925641-d598-4e5f-8b32-f2d1f4aa6b5f" alt=""><figcaption></figcaption></figure>

#### Minimum active loan size

* Each isolated market sets a minimum active loan size in USD terms at deployment (WAD), constrained within $10-100.
* Borrow checks enforce `MIN_LOAN_SIZE` through hypothetical liquidity evaluation. Partial repayments also enforce the minimum on the remaining debt, while a full repayment skips the residual-size check when `newNetDebt == 0`.

### Pausing and Permissions

**Global market toggles:** `liquidationPaused`, `redeemPaused`, and `transferPaused`.

**Per-token action toggles:** `mintPaused`, `collateralizationPaused`, and `borrowPaused`. Query them through `actionsPaused(cToken)`, which returns `(mintPaused, collateralizationPaused, borrowPaused)`.

**Listing:** Tokens must be listed a manager to be used for collateralization, borrowing, or liquidation.&#x20;

**Auction permissions:** Only addresses with auction permissions set in the `CentralRegistry` may set/reset transient auction liquidation config. Misconfigured or unauthorized auction attempts will revert.

**Position Managers:** The manager maintains an isPositionManager whitelist for advanced/leveraged workflows that require elevated privileges.

### State and Invariants

* **Collateral posted (per-account):** Tracked at the cToken layer. Query via `ICToken.collateralPosted(account)`. Market-wide posted collateral is available via `ICToken.marketCollateralPosted()`.
* **Collateral Caps:** `collateralCaps[cToken]` in shares.
* **Debt Caps:** `debtCaps[cToken]` in assets.
* **Per-token actions paused:** Visible via the manager's aggregate `actionsPaused(cToken)` view function.
* **Listing:** Manager-maintained listing state per cToken; operations revert if token is not listed.

### Cross-Contract Interactions

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FPbDUthhQXYOgFr50nxRT%2Fimage.png?alt=media&amp;token=0cb04253-11df-4588-b688-497749fb31b8" alt=""><figcaption></figcaption></figure>

The Market Manager interacts with multiple other components:

* **Central Registry:** Role/permission checks (market manager, auction permissions, timelock-controlled updates).
* **Oracle Manager:** Price feeds for risk checks and liquidation.
* **cTokens:** Accounting for balances, shares, collateral posted, borrowing, seizures, and redemptions.
* **External auction controller:** Sets transient liquidation parameters per transaction when conducting auction liquidations.

### Security Measures

* 20-minute minimum hold period across sensitive actions.
* Auction-only privileges for transient liquidation overrides.
* Bad debt socialization to protect market solvency.
* Extensive pausing controls at market and token granularity.

## User Interaction Functions

### **Core Data Query Functions**

#### isListed()

**Contract:** MarketManager

**Description:** Checks if a cToken is listed in the lending market.

**Function signature:**

```solidity
function isListed(address cToken) external view returns (bool)
```

<table><thead><tr><th width="140">Type</th><th width="175">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>cToken</td><td>Address of the cToken.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="134">Type</th><th>Description</th></tr></thead><tbody><tr><td>bool</td><td>Whether the cToken is registered in the lending market.</td></tr></tbody></table>

***

#### queryTokensListed()

**Contract:** MarketManager

**Description:** Returns a list of all tokens inside this market for&#x20;offchain querying.

**Function signature:**

```solidity
function queryTokensListed() external view returns (address[] memory)
```

**Return Data:**

<table><thead><tr><th width="155.13333129882812">Type</th><th>Description</th></tr></thead><tbody><tr><td>address[]</td><td>Array of all cToken addresses listed in the market.</td></tr></tbody></table>

***

#### assetsOf()

**Contract:** MarketManager

**Description:** Returns the cToken addresses for the account's open positions in this isolated market.

**Function signature:**&#x20;

```solidity
function assetsOf(address account) external view returns (address[] memory)
```

<table><thead><tr><th width="109">Type</th><th width="112">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>account</td><td>The address of the account to query.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="125">Type</th><th>Description</th></tr></thead><tbody><tr><td>address[]</td><td>Array of cTokens that the account has entered.</td></tr></tbody></table>

***

### Status and Liquidity

#### statusOf()

**Contract:** MarketManager

**Description:** Determines the account's current collateral value, maximum debt capacity, and current debt value.

**Function signature:**

```solidity
function statusOf(address account) external view returns (uint256, uint256, uint256)
```

<table><thead><tr><th width="104">Type</th><th width="112">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>account </td><td>The account to determine liquidity for.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="119">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>Total value of account collateral.</td></tr><tr><td>uint256</td><td>The maximum amount of debt the account could take on based on an account's collateral.</td></tr><tr><td>uint256</td><td>Total value of an account's debt.</td></tr></tbody></table>

***

### Permission and Validation

#### canMint()

**Contract:** MarketManager

**Description:** Checks whether minting is allowed for the cToken by verifying that minting is not paused and that the cToken is listed in this Market Manager. It does not take an account argument or validate account-specific state.

**Function signature:**

```solidity
function canMint(
    address cToken) external view
```

<table><thead><tr><th width="110">Type</th><th width="126">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>cToken</td><td>The Curvance token address to verify minting for.</td></tr></tbody></table>

***

#### canRedeemWithCollateralRemoval()

**Contract:** MarketManagerIsolated

**Description:** Called by a cToken to check whether an account can redeem or withdraw shares and to determine how many posted collateral shares must be removed. It checks the market-wide redeem pause, listing, transfer restrictions, hold period, and, when collateral would be removed, account liquidity.

**Function signature:**

```solidity
function canRedeemWithCollateralRemoval(
    address cToken,
    uint256 shares,
    address account,
    uint256 balanceOf,
    uint256 collateralPosted,
    bool forceRedeemCollateral
) external returns (uint256 collateralRedeemed)
```

<table><thead><tr><th width="122">Type</th><th width="139">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>cToken</td><td>The market token to verify redemption for.</td></tr><tr><td>uint256</td><td>amount</td><td>The number of tokens to redeem for the underlying asset.</td></tr><tr><td>address</td><td>account</td><td>The account which would redeem the tokens.</td></tr></tbody></table>

***

#### canRepayWithReview()

**Contract:** MarketManager

**Description:** Called by a borrowable cToken during repayment. It validates the cToken and account position, enforces the debt owner's hold period, and checks that a partial repayment does not leave a residual debt position below `MIN_LOAN_SIZE`. A full repayment with `newNetDebt == 0` skips the residual loan-size check.

**Function signature:**

```solidity
function canRepayWithReview(
    address cToken,
    uint256 newNetDebt,
    address debtAsset,
    uint256 decimals,
    address account
) external view
```

<table><thead><tr><th width="125">Type</th><th width="124">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>cToken</td><td>The market token to verify the repayment for.</td></tr><tr><td>address</td><td>account</td><td>The account who will have their loan repaid.</td></tr></tbody></table>

***

### Paused States

#### actionsPaused()

**Contract:** MarketManager

**Description:** Checks whether minting, collateralization, borrowing of cToken is paused.

```solidity
function actionsPaused(
    address cToken
) external view returns (bool, bool, bool);
```

**Inputs:**

<table><thead><tr><th width="109.20001220703125">Type</th><th width="469.98333740234375">Description</th></tr></thead><tbody><tr><td>address</td><td>Address of the cToken to check the pause statuses of.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="109.20001220703125">Type</th><th width="469.98333740234375">Description</th></tr></thead><tbody><tr><td>bool</td><td>Whether minting cToken is paused or not.</td></tr><tr><td>bool</td><td>Whether collateralization of cToken is paused or not.</td></tr><tr><td>bool</td><td>Whether borrowing cToken is paused or not.</td></tr></tbody></table>

***

### Market Invariants

**Contract:** cTokens

**Description:** Per-account posted collateral is tracked on each cToken, not on the Market Manager. Query `collateralPosted(account)` on the cToken for an account's posted shares. Query `marketCollateralPosted()` on the cToken for market-wide posted shares.

**Function signatures:**

```solidity
function collateralPosted(address account) external view returns (uint256);

function marketCollateralPosted() external view returns (uint256);
```

**collateralPosted inputs:**

<table><thead><tr><th width="109.20001220703125">Type</th><th width="469.98333740234375">Description</th></tr></thead><tbody><tr><td>address</td><td>The account to check for posted collateral shares.</td></tr></tbody></table>

**collateralPosted return data:**

<table><thead><tr><th width="109.20001220703125">Type</th><th width="469.98333740234375">Description</th></tr></thead><tbody><tr><td>uint256</td><td>Amount of cToken shares posted as collateral.</td></tr></tbody></table>

***

#### collateralCaps()

**Contract:** MarketManager

**Description:** Returns the amount of cToken that can be posted of collateral, in shares.

```solidity
mapping(address => uint256) public collateralCaps;
```

**Inputs:**

<table><thead><tr><th width="109.20001220703125">Type</th><th width="469.98333740234375">Description</th></tr></thead><tbody><tr><td>address</td><td>Address of the cToken to check the max amount of shares that can be posted as collateral.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="109.20001220703125">Type</th><th width="469.98333740234375">Description</th></tr></thead><tbody><tr><td>uint256</td><td>amount of cTokens that has can be posted as collateral.</td></tr></tbody></table>

***

#### debtCaps()

**Contract:** MarketManager

**Description:** Returns the amount of cToken that can be borrowed, in underlying assets.

```solidity
mapping(address => uint256) public debtCaps;
```

**Inputs:**

<table><thead><tr><th width="109.20001220703125">Type</th><th width="469.98333740234375">Description</th></tr></thead><tbody><tr><td>address</td><td>Address of the cToken</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="109.20001220703125">Type</th><th width="469.98333740234375">Description</th></tr></thead><tbody><tr><td>uint256</td><td>amount of cTokens that can be borrowed.</td></tr></tbody></table>


# cToken

## Overview

cTokens are Curvance vault tokens that represent user deposits inside an isolated lending market. Each cToken wraps one underlying asset. Some cTokens are simple wrappers, some are strategy vaults that harvest external rewards, and BorrowableCTokens accrue borrower interest for lenders. cTokens are designed so supported asset positions can be made available for protocol withdrawals and liquidations, but user withdrawals still depend on available liquidity, account cooldown state, collateral health, and pause status.

{% hint style="info" %}
While CTokens follow the ERC-4626 standard in spirit, they are **not fully ERC-4626 compliant**. Certain view functions include stricter reentrancy protections, and zero-amount transfers are intentionally blocked to minimize edge cases. These design decisions are made to prioritize security, predictable behavior, and flexible liquidity management.
{% endhint %}

## Core Architecture

CTokens follow a hierarchical inheritance structure designed for flexibility and extensibility:

1. **BaseCToken:** The foundational abstract contract implementing ERC4626 vault functionality and core Curvance protocol integration.
2. **SimpleCToken:** For basic assets that don't generate external rewards (e.g., WETH, stablecoins, liquid staking tokens).
3. **StrategyCToken:** Extended functionality for assets that generate yield, supporting automatic reward harvesting and compounding.
4. **StrategyCTokenWithExitFee:** Adds an exit fee mechanism to compounding vaults for strategies that require it.
5. **Protocol-Specific Implementations:** Custom implementations tailored for different DeFi protocols and yield strategies.

## Key Contracts and Interactions

CTokens integrate with several core Curvance contracts to provide comprehensive functionality:

* **CentralRegistry:** Protocol configuration and permissions management, ensuring proper authorization and global settings.
* **MarketManager:** Handles collateral position registration, risk parameters, and borrowing authorization.
* **Oracle System:** Provides accurate asset valuation for collateral and liquidation purposes.
* **External Protocol Contracts:** For staking, providing liquidity, and claiming rewards across various DeFi ecosystems.

## Types of cTokens

### Simple cTokens

Simple CTokens (SimpleCToken) are designed for assets that don't generate external rewards. They provide a straightforward vault wrapper for assets such as:

* Wrapped native assets
* Liquid Staking Tokens (LSTs)
* Principal tokens from yield-bearing assets
* Stablecoins and yield-bearing stablecoins
* Basic wrapped tokens

These vaults focus on capital efficiency and gas optimization.

### Strategy cTokens

Strategy cTokens (`StrategyCToken`) extend the base vault with permissioned harvest and compounding logic. These vaults:

* Allow approved harvest operators to call `harvest(bytes)` when harvesting is unpaused and the previous vesting period has finished.
* Claim rewards from the underlying protocol.
* Apply any strategy harvest fee in the reward token and send the fee to the fee manager.
* Swap or route the remaining rewards into the strategy's underlying position.
* Deposit the new strategy assets and vest the harvested yield over the configured vesting period.

### BorrowableCTokens

See [BorrowableCTokens](#borrowablectokens).

### Protocol-Specific Implementations

Curvance offers various protocol-specific CToken implementations tailored to different DeFi protocols and their unique characteristics.

Each implementations handles the specific deposit, withdrawal, reward harvesting, and risk management logic optimized for its respective protocol.

## State Managemecont

CTokens maintain several key state variables to track vault operations and user positions:

```solidity
// BaseCToken
IMarketManager public immutable marketManager;
uint256 public marketCollateralPosted;   // total shares posted as collateral in this cToken
mapping(address => uint256) public collateralPosted;   // account => collateralized shares
uint256 internal _totalAssets;           // minus any pending vesting
uint256 internal constant _BALANCE_SLOT_SEED = 0x87a211a2;
```

```solidity
// BaseCTokenWithYield (inherited by StrategyCToken and BorrowableCToken; not SimpleCToken)
uint256 public vestingPeriod;
uint256 internal _vestingData;           // packed vesting data; layout differs per subclass (see below)
```

## Data Flow

### Deposit Flow

1. User calls `deposit(assets, receiver)` with underlying assets.
2. The cToken accrues pending yield or interest, calculates shares from the updated asset accounting, and checks `marketManager.canMint(address(this))`.
3. The underlying tokens are transferred from the caller to the cToken.
4. The cToken updates `_totalAssets`. Strategy cTokens then use `_afterDeposit(assets, 0)` from `_updateAssetsForDeposit` to place the assets into the external strategy.
5. Vault shares are minted to `receiver`, and the cToken emits `Deposit`.
6. If the caller uses `depositAsCollateral` or `depositAsCollateralFor`, the cToken then posts the minted shares as collateral through `marketManager.canCollateralize`.

### Withdrawal Flow

1. User calls `withdraw(assets, receiver, owner)` or `redeem(shares, receiver, owner)`.
2. The cToken accrues pending yield or interest, calculates the shares or assets, checks allowance when `msg.sender != owner`, and asks the Market Manager whether the redemption is allowed.
3. For non-force withdrawals or redeems, posted collateral can still be removed if the requested shares exceed the owner's idle share balance and the Market Manager health check allows the collateral removal.
4. The cToken burns the owner's shares, updates `_totalAssets`, and then prepares or transfers underlying. Strategy cTokens call `_beforeWithdraw(assets, 0)` from `_updateAssetsForWithdrawal` before the underlying is sent to `receiver`.
5. The cToken emits `Withdraw`. If collateral was removed, it also emits `CollateralUpdated`.

### Collateral Management Flow

1. User calls `postCollateral()` to use cTokens as borrowing collateral.&#x20;
2. The cToken asks its Market Manager to verify that collateralization is unpaused and that the new market-wide posted-share total does not exceed `collateralCaps(cToken)`.
3. MarketManager is notified of the new collateral position.
4. User's cTokens are marked as collateral and transfer-restricted.
5. Collateral is now available for borrowing calculations.
6. User can borrow against collateralized position through BorrowableCTokens.

### Reward Harvesting Flow (Strategy Vaults)

1. `harvest()` function is called by authorized keepers or DAO.
2. External rewards are claimed from the underlying protocols.
3. Strategy harvests apply the configured strategy harvest fee in the reward token and transfer that fee to the fee manager. BorrowableCTokens do not use `harvest`; during interest accrual, a protocol interest fee can be minted as new cToken shares to the DAO address.
4. Rewards are swapped to underlying assets through optimized routes.
5. New assets are deposited back into the yield strategy.
6. Yield is distributed to vault users through vesting mechanism.
7. Exchange rate increases, benefiting all cToken holders.

### Yield Distribution Mechanism

Strategy cTokens and BorrowableCTokens employ a sophisticated vesting approach to distribute yield fairly and prevent MEV exploitation:

* **Gradual Distribution:** Newly recognized assets vest linearly over the vault's vesting period. For StrategyCTokens this is the configured `vestingPeriod`, which must be greater than zero and no more than 3 days and can be scheduled for update by governance. For BorrowableCTokens, the period is the linked IRM's `ADJUSTMENT_RATE`, which is currently `10 minutes` in `DynamicIRM`.
* **MEV Protection:** This creates a smoother increase in share price and reduces opportunities for front-running harvest transactions.
* **Fair Distribution:** All users benefit proportionally from yield regardless of when they deposited relative to harvest timing.

The vesting mechanism operates through packed data in `_vestingData`.

**In StrategyCTokens:**

* **`VESTING_RATE` (bits 0-175):** Rate at which rewards vest per second.
* **`VEST_END` (bits 176-215):** When current vesting period ends.
* **`LAST_VEST` (bits 216-255):** Last time vested rewards were claimed.

**In BorrowableCTokens:**

* **`VESTING_RATE` (bits 0-95):** Rate at which rewards vest per second.
* **`VEST_END` (bits 96-135):** When current vesting period ends.
* **`LAST_VEST` (bits 136-175):** Last time vested rewards were claimed.
* `DEBT_INDEX` **(bits 176-255):** Cumulative interest index used to calculate accrued interest on debt positions.

The `accrueIfNeeded()` function is called to update internal yield accounting and ensure vesting data reflects the current state.

### Security Features

cTokens incorporate multiple layers of security protection:

* **Collateral Restrictions:** Transfers route through the Market Manager. If an account transfers more than its idle shares, the transfer can remove posted collateral only when the hold-period and liquidity checks allow it. Otherwise, the transfer reverts.
* **Pause Mechanisms:** Market Managers expose market-wide `liquidationPaused`, `redeemPaused`, and `transferPaused` flags, and token-specific `mintPaused`, `collateralizationPaused`, and `borrowPaused` flags. Strategy cTokens also expose `harvestingPaused`.
* **Reentrancy Protection:** All critical functions implement comprehensive reentrancy guards.
* **Slippage Protection:** Swap operations and liquidity management include minimum output requirements and deadline protection.
* **Chain Validation:** Protocol-specific implementations validate deployment on correct networks.
* **Permissioned Operations:** Sensitive functions like harvesting and emergency actions are restricted to authorized addresses.
* **Time Locks:** Certain operations include minimum duration requirements to prevent flash loan attacks.

### Oracle Integration

cTokens integrate deeply with Curvance's oracle infrastructure for accurate and secure valuation:

* **Protocol-Specific Adaptors:** Custom oracle adaptors for complex assets like LP tokens, principal tokens, and yield-bearing assets.
* **TWAP Support:** Time-weighted average prices provide protection against price manipulation attacks.
* **Chainlink-Based Feeds:** Each priced asset routes through a single Chainlink-like adaptor configured for the asset. Staleness, heartbeat, and (for pegged assets) price-guard checks are applied on every read.
* **Price Guards:** For pegged assets (LSTs and stablecoins), an additional guard constrains the reported price inside a bounded range relative to a reference feed.

### Considerations

**Collateral Caps:** Each isolated Market Manager stores `collateralCaps(cToken)` in cToken shares. The cap limits how many shares of that cToken can be posted as collateral in that market; it is not a global protocol-wide deposit cap.

**Minimum duration requirements:** Posting collateral or borrowing starts an account-level 20-minute cooldown. During that window, repayment and cToken transfer, redeem, and withdraw paths that call the Market Manager checks revert. Deposits are not blocked by the cooldown.

**Gas Optimization:** Functions are designed to minimize gas costs while maintaining security and functionality.

**Yield Optimization:** Strategy implementations are regularly evaluated and updated to maximize user returns while managing risk.

***

## User Interaction Functions

### Share/Assets

{% hint style="info" %}
**A note on accrual and previews:**

`convertToShares`, `convertToAssets`, and the `preview*` functions do not trigger interest/yield accrual and operate on the last accrued state. `convertTo*`, `preview*`, `totalAssets`, and `exchangeRate()` read the last-accrued state and therefore drift between on-chain touches. For reads that reflect the current block, integrators should `staticCall` `exchangeRateUpdated()` or `getSnapshotUpdated(account)`; either triggers accrual inside the simulated call. Broadcasting an `accrueIfNeeded()` transaction is not a free way to refresh a view: by the time the next block arrives, the state has already drifted again. See the "Stale vs Fresh Reads" section in the integration cookbook for the canonical write-up.
{% endhint %}

#### convertToShares()

**Contract:** BaseCToken.sol

**Description:** The number of underlying tokens to theoretically convert to cToken shares.

**Function signature:**

```solidity
function convertToShares(uint256 assets) public view returns (uint256)
```

<table><thead><tr><th width="109">Type</th><th width="102">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>amount</td><td>The number of underlying tokens to theoretically convert to cTokens.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="106">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The number of cTokens a user would receive for the given amount of underlying</td></tr></tbody></table>

***

#### convertToAssets()

**Contract:** BaseCToken.sol

**Description:** The number of cToken shares to theoretically convert to underlying assets.

**Function signature:**

```solidity
function convertToAssets(uint256 shares) public view returns (uint256) 
```

<table><thead><tr><th width="109">Type</th><th width="102">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>tokens</td><td>The number of cToken shares to theoretically convert to underlying assets.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="106">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The number of underlying assets the user would receive for redeeming shares.</td></tr></tbody></table>

#### previewDeposit()

**Contract:** BaseCToken.sol

**Description:** Allows users to simulate the effects of their deposit at the current block.

**Function signature:**

```solidity
function previewDeposit(
    uint256 assets
) public view override returns (uint256) 
```

<table><thead><tr><th width="109">Type</th><th width="102">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assets</td><td>The number of assets to preview a deposit call.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="106">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The shares received for depositing <code>assets</code>.</td></tr></tbody></table>

#### previewMint()

**Contract:** BaseCToken.sol

**Description:** Allows users to simulate the assets required to mint `shares` at the current block.

**Function signature:**

```solidity
function previewMint(
    uint256 shares
) public view override returns (uint256)
```

<table><thead><tr><th width="109">Type</th><th width="102">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>shares</td><td>The number of assets, quoted as shares to preview a mint call.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="106">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The assets required to mint <code>shares</code>.</td></tr></tbody></table>

#### previewWithdraw()

**Contract:** BaseCToken.sol

**Description:** Allows users to simulate the effects of their withdraw at the current block.

**Function signature:**

```solidity
function previewWithdraw(
    uint256 assets
) public view virtual override returns (uint256) 
```

<table><thead><tr><th width="109">Type</th><th width="102">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>tokens</td><td>The number of assets to preview a withdraw call.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="106">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The shares required to withdraw <code>assets</code>.</td></tr></tbody></table>

#### previewRedeem()

**Contract:** BaseCToken.sol

**Description:** Converts an amount of cToken shares to equivalent underlying assets using the current exchange rate.

**Function signature:**

```solidity
function previewRedeem(
    uint256 shares
) public view virtual override returns (uint256) 
```

<table><thead><tr><th width="109">Type</th><th width="102">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>shares</td><td>The number of shares to preview a redeem call.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="106">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The number of underlying assets the user would receive for redeeming shares.</td></tr></tbody></table>

***

### Deposits

#### deposit()

**Contract:** BaseCToken.sol

**Description:** Deposits assets into the vault and receives shares.

**Function signature:**

```solidity
function deposit(uint256 assets, address receiver) external returns (uint256 shares)
```

<table><thead><tr><th width="125">Type</th><th width="110">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assets</td><td>The amount of underlying assets to deposit.</td></tr><tr><td>address</td><td>receiver</td><td>The account that should receive the cToken shares.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="121.98333740234375">Type</th><th width="118.70001220703125">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>shares</td><td>The amount of cToken shares received by receiver.</td></tr></tbody></table>

**Events:**

```solidity
// From cToken
event Deposit(address indexed caller, address indexed receiver, uint256 assets, uint256 shares);
event Transfer(address indexed from, address indexed to, uint256 value);
```

***

#### depositAsCollateral()

**Contract:** BaseCToken.sol

**Description:** The depositAsCollateralFor function enables users to deposit assets and automatically post them as collateral on behalf of another address, returning the amount of pToken shares received by the receiver.

**Function signature:**

```solidity
function depositAsCollateral(
    uint256 assets, 
    address receiver) external returns (uint256 shares)
```

<table><thead><tr><th width="119">Type</th><th width="140">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assets</td><td>The amount of underlying assets to deposit.</td></tr><tr><td>uint256</td><td>receiver</td><td>The account that should receive the cToken shares.</td></tr></tbody></table>

**Return Data:**

<table><thead><tr><th width="124">Type</th><th width="139">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>shares</td><td>The amount of cToken shares received by receiver.</td></tr></tbody></table>

**Events:**

<pre class="language-solidity"><code class="lang-solidity"><strong>// From cToken
</strong>event Deposit(address indexed caller, address indexed receiver, uint256 assets, uint256 shares);
event Transfer(address indexed from, address indexed to, uint256 value);
</code></pre>

```solidity
// Only emitted if this is the user's first position in this cToken.
event PositionUpdated(address cToken, address account, bool open);
```

***

#### depositAsCollateralFor()

**Contract:** BaseCToken.sol

**Description:** The `depositAsCollateralFor` function enables users to deposit assets and automatically post them as collateral on behalf of another address, returning the amount of pToken shares received by the receiver. Unlike `depositAsCollateral`, this function requires explicit delegation permission, where the receiver must have previously approved the caller to act on their behalf. This makes it particularly useful for smart contract integrations, portfolio management tools, and protocols that assist users in capital optimization. The function operates by first checking delegation permissions, then transferring assets from the caller to the vault, minting shares to the receiver, and finally posting those shares as collateral through the `marketManager`.&#x20;

{% hint style="warning" %}
Users should exercise caution when delegating this permission, as delegates could potentially abuse it by repeatedly posting shares as collateral, which could temporarily prevent withdrawals and effectively lock a user's funds. If the caller lacks proper delegation permission, the call reverts with `PluginDelegable__Unauthorized()` before any deposit occurs. The receiver must first call `setDelegateApproval(caller, true)` on this cToken.
{% endhint %}

**Function signature:**

```solidity
function depositAsCollateralFor(
    uint256 assets,
    address receiver
) external nonReentrant returns (uint256 shares);
```

<table><thead><tr><th width="119">Type</th><th width="140">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assets</td><td>The amount of underlying assets to deposit.</td></tr><tr><td>uint256</td><td>receiver</td><td>The account that should receive the cToken shares.</td></tr></tbody></table>

**Return Data:**

<table><thead><tr><th width="124">Type</th><th width="139">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>shares</td><td>The amount of cToken shares received by receiver.</td></tr></tbody></table>

**Events:**

<pre class="language-solidity"><code class="lang-solidity"><strong>// From cToken
</strong>event Deposit(address indexed caller, address indexed receiver, uint256 assets, uint256 shares);
event Transfer(address indexed from, address indexed to, uint256 value);
</code></pre>

```solidity
// From MarketManager
event PositionUpdated(address cToken, address account, bool open);
```

***

#### postCollateral()

Contract: BaseCToken.sol Description: Marks an existing cToken balance as collateral, making it available for borrowing against inside the same isolated market. Reverts if the cToken has `collRatio == 0`, if the market's `collateralCap` would be exceeded, or if the account does not hold enough un-posted shares. Triggers the 20-minute hold period on the account's collateralized shares. Function signature:

```solidity
function postCollateral(uint256 shares) external nonReentrant
```

Events:

```solidity
event CollateralUpdated(uint256 shares, bool increased, address account);   // from cToken
event PositionUpdated(address cToken, address account, bool open);          // from MarketManager, only on first-time posting
```

***

#### mint()

**Contract:** BaseCToken.sol

**Description:** Caller deposits the asset amount required to mint `shares`, and `receiver` receives the minted shares. Returns the underlying assets consumed.

**Function signature:**

```solidity
function mint(uint256 shares, address receiver) public nonReentrant returns (uint256 assets)
```

<table><thead><tr><th width="99">Type</th><th width="100">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>amount</td><td>The amount of the underlying asset to deposit</td></tr></tbody></table>

**Return Data:**

<table><thead><tr><th width="97">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The amount of cTokens minted</td></tr></tbody></table>

**Events:**

```solidity
// Emitted from cToken and underlying asset.
event Transfer(address indexed from, address indexed to, uint256 value);
```

***

#### postCollateralFor()

Contract: BaseCToken.sol Description: Same as `postCollateral`, but posts shares already owned by `owner` on behalf of that owner. The caller must first have been approved via `setDelegateApproval(caller, true)` on this cToken by `owner`; if not, the call reverts with `PluginDelegable__Unauthorized()`. Powers portfolio-management and position-manager integrations. Delegates can delay withdrawals for the owner by repeatedly posting collateral, so owners should only approve addresses they trust. Function signature:

```solidity
function postCollateralFor(uint256 shares, address owner) external nonReentrant
```

Events: same as `postCollateral`.

***

### Withdrawals

#### withdraw()

**Contract:** BaseCToken.sol

**Description:** Facilitates the withdrawal of assets from the market and the burning of shares. Initially, it verifies if the owner is eligible to redeem shares within the given market. Upon successful validation, it proceeds to burn the shares and subsequently returns the asset. Importantly, it does not force the withdrawals. If the caller is withdrawing another owner's cTokens, they must first have enough approval.&#x20;

**Function signature:**

```solidity
    function withdraw(
        uint256 assets,
        address receiver,
        address owner
    ) public override nonReentrant returns (uint256 shares) {
```

<table><thead><tr><th width="119">Type</th><th width="125">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assets</td><td>The amount of underlying assets to withdraw.</td></tr><tr><td>address</td><td>receiver</td><td>The account that should receive the assets.</td></tr><tr><td>address</td><td>owner</td><td>The account that will burn their shares to withdraw assets.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="134">Type</th><th width="104">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>shares</td><td>The amount of shares that were burned.</td></tr></tbody></table>

**Events:**

```solidity
// From cToken
event Withdraw(address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares);
event Transfer(address indexed from, address indexed to, uint256 value);
```

```solidity
// From MarketManager
event PositionUpdated(address cToken, address account, bool open);
```

***

#### redeem()

**Function:** Burns shares to receive assets.

**Contract:** BaseCToken.sol

**Function signature:**

```solidity
function redeem(
    uint256 shares, 
    address receiver, 
    address owner) external returns (uint256 assets)
```

<table><thead><tr><th width="128.183349609375">Type</th><th width="130.45001220703125">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>shares</td><td>The amount of shares to redeem.</td></tr><tr><td>address</td><td>receiver</td><td>The account that should receive the assets.</td></tr><tr><td>address</td><td>owner</td><td>The account that will burn their shares to receive assets.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="137.11666870117188">Type</th><th width="118.75">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assets</td><td>The amount of underlying assets sent to the receiver.</td></tr></tbody></table>

**Events:**

```solidity
// From cToken
event Withdraw(address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares);
event Transfer(address indexed from, address indexed to, uint256 value);
```

```solidity
// From MarketManager
event PositionUpdated(address cToken, address account, bool open);
```

***

#### redeemCollateral()

**Description:** Redeems collateralized shares to receive assets.

**Contract:** BaseCToken.sol

**Function signature:**

```solidity
function redeemCollateral(
    uint256 shares,
    address receiver,
    address owner
) external nonReentrant returns (uint256 assets)
```

<table><thead><tr><th width="121.48330688476562">Type</th><th width="122.28338623046875">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>shares</td><td>The amount of collateralized shares to redeem.</td></tr><tr><td>address</td><td>receiver</td><td>The account that should receive the assets.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="133.76666259765625">Type</th><th width="109.58331298828125">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assets</td><td>The amount of underlying assets sent to the receiver.</td></tr></tbody></table>

**Events:**

```solidity
// From cToken
event Withdraw(address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares);
event Transfer(address indexed from, address indexed to, uint256 value);
```

```solidity
// From MarketManager
// Always emitted because collateral is being removed.
event PositionUpdated(address cToken, address account, bool open);
```

***

#### redeemFor()

**Description:** Withdraws assets, quoted in shares from the market, and burns owner shares, on behalf of owner.

**Contract:** BaseCToken.sol

**Function signature:**

```solidity
function redeemFor(
        uint256 shares,
        address receiver,
        address owner) public returns (uint256 assets)
```

<table><thead><tr><th width="121.48330688476562">Type</th><th width="122.28338623046875">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>shares</td><td>The amount of collateralized shares to redeem.</td></tr><tr><td>address</td><td>receiver</td><td>The account that should receive the assets.</td></tr><tr><td>address</td><td>owner</td><td>The account that will burn their shares to withdraw assets.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="133.76666259765625">Type</th><th width="109.58331298828125">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assets</td><td>The amount of underlying assets sent to the receiver.</td></tr></tbody></table>

**Events:**

```solidity
// From cToken
event Withdraw(address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares);
event Transfer(address indexed from, address indexed to, uint256 value);
```

```solidity
// From MarketManager
event PositionUpdated(address cToken, address account, bool open);
```

***

**redeemCollateralFor()**

**Description:** Redeems collateralized shares on behalf of an owner.

**Contract:** BaseCToken.sol

**Function signature:**

```solidity
function redeemCollateralFor(
    uint256 shares,
    address receiver, 
    address owner) external returns (uint256 assets)
```

<table><thead><tr><th width="134">Type</th><th width="122">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>shares</td><td>The amount of collateralized shares to redeem.</td></tr><tr><td>address</td><td>receiver</td><td>The account that should receive the assets.</td></tr><tr><td>address</td><td>owner</td><td>The account that owns the shares being redeemed.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="128.183349609375">Type</th><th width="108.11669921875">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assets</td><td>The amount of underlying assets sent to the receiver.</td></tr></tbody></table>

**Events:**

```solidity
// From cToken
event Withdraw(address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares);
event Transfer(address indexed from, address indexed to, uint256 value);
```

```solidity
// From MarketManager
// Always emitted because collateral is being removed.
event CollateralUpdated(uint256 shares, bool increased, address account);
```

***

#### removeCollateral()

**Contract:** BaseCToken.sol Description: Un-posts `shares` from the caller's collateral position inside this market, returning them to freely-transferable cToken balance. Reverts if removing the shares would leave the account under-collateralized against outstanding debt in any BorrowableCToken in the same market (`marketManager.canRedeemWithCollateralRemoval` check). Does not burn shares or move underlying. Function signature:

```solidity
function removeCollateral(uint256 shares) external nonReentrant
```

Events:

```solidity
event CollateralUpdated(uint256 shares, bool increased, address account);   // from cToken, increased = false
event PositionUpdated(address cToken, address account, bool open);          // from MarketManager, only when position fully closes
```

***

#### removeCollateralFor()

**Contract:** BaseCToken.sol Description: Same as `removeCollateral`, but un-posts shares from `owner`'s collateral position on their behalf. The caller must be an approved delegate of `owner` (`setDelegateApproval`); if not, the call reverts with `PluginDelegable__Unauthorized()`. Function signature:

```solidity
function removeCollateralFor(uint256 shares, address owner) external nonReentrant
```

Events: same as `removeCollateral`.

***

#### withdrawCollateral()

**Contract:** BaseCToken.sol Description: Burns enough of `owner`'s shares to return exactly `assets` of the underlying to `receiver`, forcing collateral to be removed from `owner`'s collateral position if the free (un-posted) share balance is insufficient. This is the force-redeem assets variant; `withdraw()` by contrast never touches collateral and will revert with `InsufficientLiquidity` if the caller tries to withdraw more than their free balance. Reverts if removing the shares would leave the account under-collateralized against outstanding debt. If `msg.sender != owner`, a standard ERC20 share allowance is required; delegation does not apply to this function. Function signature:

```solidity
function withdrawCollateral(
    uint256 assets,
    address receiver,
    address owner
) external nonReentrant returns (uint256 shares)
```

Events:

```solidity
event Withdraw(address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares);
event Transfer(address indexed from, address indexed to, uint256 value);   // burn, from = owner, to = address(0)
event CollateralUpdated(uint256 shares, bool increased, address account);  // from cToken, only if collateral had to be pulled
event PositionUpdated(address cToken, address account, bool open);         // from MarketManager, only if position fully closes
```

***

### Exchange Rate Calculation

#### exchangeRate()

**Contract:** cToken

**Description:** Returns the current exchange rate without updating interest, showing how many underlying tokens each cToken is worth in WAD format (view function).

**Function signature:**

```solidity
function exchangeRate() external view returns (uint256)
```

**Return data:**

<table><thead><tr><th width="118">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>Exchange rate from shares to assets in WAD format</td></tr></tbody></table>

***

#### exchangeRateUpdated()

**Contract:** cToken

**Description:** Returns the current exchange rate after updating interest via `_accrueIfNeeded()`, used to determine how many underlying tokens each cToken is worth.

**Function signature:**

```solidity
function exchangeRateUpdated() external returns (uint256)
```

**Return data:**

<table><thead><tr><th width="118">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>Exchange rate from shares to assets in WAD format.</td></tr></tbody></table>

***

### Interest Accrual Mechanism

Interest/yield is accrued on BorrowableCTokens/StrategyCTokens and distributed to cToken holders through the increasing exchange rate. The accrual process is managed by the `accrueIfNeeded` function.

#### accrueIfNeeded()

**Contract: BaseCToken.sol**

**Function signature:**

```solidity
function accrueIfNeeded() public
```

In BorrowableCToken, this function:

1. Calculates time elapsed since the last interest accrual.
2. Fetches the current interest rate from the interest rate model.
3. Computes the interest amount based on current total borrows.
4. Updates total borrows with the new interest.
5. Allocates a portion of interest to protocol reserves based on the `interestFee`.
6. Updates the exchange rate to reflect the new value of each BorrowableCToken.

In StrategyCTokens, this function vests previously harvested yield into `_totalAssets` based on elapsed time inside the current vesting window. It is called internally by `mint`, `deposit`, `redeem`, `withdraw`, and transfer flows. Strategy cTokens do not have `borrow` or `repay`; only `BorrowableCToken` does.

***

### Compounding Vault Specifics

For compounding vaults like PendleLPCToken, VelodromeVolatileCToken, AerodromeStableCToken.

#### Vesting Mechanism

Compounding vaults use a vesting mechanism to gradually distribute yield.

When yield is harvested:

1. Rewards are claimed from the external protocol.
2. A protocol fee is taken.
3. Remaining rewards are swapped to the underlying asset.
4. The yield is distributed over the vesting period (default 1 day).

```solidity
function harvest(bytes calldata data) external returns (uint256 yield)
```

<table><thead><tr><th width="157">Parameter</th><th>Description</th></tr></thead><tbody><tr><td>data</td><td>Encoded swap parameters to convert rewards to underlying assets.</td></tr></tbody></table>

#### Getting Vault Status

`getYieldInformation()` has two forms. Both live under the same "Getting Vault Status" slot but return different values depending on the cToken subclass.

**BorrowableCToken (borrowable markets, e.g. cUSDC, cWETH, cWBTC):**

```solidity
function getYieldInformation() external view nonReadReentrant returns (
    uint256 vestingRate,
    uint256 vestingEnd,
    uint256 lastVestingClaim,
    uint256 debtIndex
);
```

* `vestingRate`: borrower interest vested per second, scaled by `WAD`. Applied against `marketOutstandingDebt`, not against `_totalAssets`.
* `vestingEnd`: timestamp at which the current IRM adjustment window ends and rates recompute.
* `lastVestingClaim`: timestamp at which pending accrued interest was last rolled into `marketOutstandingDebt` and `_totalAssets`.
* `debtIndex`: the market-wide cumulative debt index. A borrower's current debt is `storedDebt * currentDebtIndex / borrowerDebtIndex`.

**StrategyCToken (compounding vaults, e.g. PendleLPCToken, AerodromeStableCToken, VelodromeVolatileCToken):**

```solidity
function getYieldInformation() external view nonReadReentrant returns (
    uint256 vestingRate,
    uint256 vestingEnd,
    uint256 lastVestingClaim,
    uint256 strategyPaused
);
```

* `vestingRate`: yield vested per second, in `asset()`, scaled by `WAD` (1e18).
* `vestingEnd`: timestamp at which the current vesting window ends and the next harvest becomes possible.
* `lastVestingClaim`: timestamp at which pending vested yield was last rolled into `_totalAssets`.
* `strategyPaused`: harvesting pause state. `1 = unpaused`, `2 = paused`. (Stored as a `uint256` sentinel, not a `bool`, for gas reasons.)

**Why the 4th slot differs:** the two subclasses pack `_vestingData` differently. StrategyCToken uses 176 bits for `vestingRate` (external yield can be large and arbitrary-denominated before fee), leaving 40+40 bits for the two timestamps and no room for an index. BorrowableCToken uses 96 bits for `vestingRate` (interest-per-second on a bounded debt pool fits), 40+40 for timestamps, and reuses the top 80 bits for `debtIndex` so borrower debt calculations don't need a second storage slot. Consumers should dispatch on `isBorrowable()` to know which shape to expect.

## Considerations

* **Collateral Caps**: Restricts the total exogenous risk from any single asset.
* **20-Minute Hold Period:** Posting collateral or opening a borrow starts a 20-minute window during which the account's collateralized share transfers and debt repayment are blocked. The hold does not prevent deposits or non-collateralized withdrawals.
* **Safe Functions**: Enhanced protection against reentrancy and other vulnerabilities.
* **Liquidation Safeguards**: Only authorized contracts can seize collateral during liquidations.


# BorrowableCToken

## Overview

BorrowableCTokens are Curvance's lending market tokens that enable users to earn interest on deposited assets while simultaneously creating borrowing liquidity for the protocol. These tokens extend the base cToken functionality with comprehensive lending market features, including borrowing, interest accrual, liquidations, and flash loans. Users who deposit underlying assets receive BorrowableCTokens representing their share of the lending pool, which increase in value as borrowers pay interest.

### Core Architecture

BorrowableCTokens build on the base cToken foundation by extending `BaseCTokenWithYield`, which itself extends `BaseCToken`.

* **BaseCToken:** Provides the ERC4626-style vault mechanics, collateral accounting, Market Manager integration, and transfer/redemption checks.
* **BaseCTokenWithYield:** Adds the packed vesting state used by strategy yield and BorrowableCToken interest accrual. For BorrowableCTokens, the packed data also stores the market debt index.
* **BorrowableCToken**: Adds lending market functionality including:
  * Borrow against collateral
  * Interest rate management
  * Debt tracking and liquidations
  * Flash loan capabilities
  * Protocol fee collection

### Key Integrations

BorrowableCTokens operate within a sophisticated network of contracts:

* MarketManager: Manages risk parameters, collateral requirements, borrowing authorization, and liquidation processes.
* DynamicIRM (Interest Rate Model): Determines borrowing and lending rates based on market utilization and other factors.
* CentralRegistry: Provides protocol-wide configuration, permissions, and fee settings.
* Oracle System: Supplies accurate asset pricing for collateral valuation and liquidation triggers.
* Position Managers: Enable complex leveraged positions and automated strategies.

### State Management

BorrowableCTokens maintain sophisticated state tracking for lending operations:

```solidity
// Constants
uint256 public constant MAX_INTEREST_ACCRUAL_FEE = 6000;   // 60% in BPS
uint256 public constant FLASHLOAN_FEE = 4;                 // 0.04% in BPS

// Storage
IDynamicIRM public IRM;
uint240 public marketOutstandingDebt;    // total borrowed underlying, in assets
uint16 public interestFee;               // protocol's % of accrued interest, BPS

// Packed: [0..175] account DEBT, [176..255] account DEBT_INDEX
mapping(address => uint256) internal _debtOf;

// Inherited from BaseCTokenWithYield, BorrowableCToken layout:
//   [0..95]   VESTING_RATE
//   [96..135] VEST_END
//   [136..175] LAST_VEST
//   [176..255] market DEBT_INDEX
uint256 internal _vestingData;
```

### Interest Rate Mechanism

BorrowableCTokens employ a dynamic interest rate system that responds to market conditions:

* **Utilization-Based Rates:** Interest rates adjust based on market utilization, computed as `debt / (assetsHeld + debt)`. `assetsHeld()` is the borrowable liquidity available after excluding outstanding debt and the base underlying reserve.
* **Accrual-Driven Adjustments:** The DynamicIRM does not monitor markets autonomously. `BorrowableCToken._accrueIfNeeded()` calls `IRM.adjustedBorrowRate(...)` when the current vesting window has ended, and the new per-second borrow rate is packed into `_vestingData`.
* **Dual Rate System:**
  * **Borrow Rate:** Interest rate paid by borrowers, in WAD per second. Computed by the DynamicIRM from current utilization, configured base and vertex rates, and the current vertex multiplier. The next rate is applied when a cToken accrual crosses an adjustment window.
  * **Supply Rate:** Interest rate earned by lenders, in `WAD` per second. Implicit in the rising exchange rate. Equal to `borrowRate * utilization * (1 - interestFee)`, where `interestFee` is the protocol's cut.

### Data Flow

#### Lending (Deposit) Flow

1. User calls `deposit()` or  `mint()` with underlying assets.
2. Contract accrues any pending interest.
3. Contract calculates shares based on current exchange rate.
4. Underlying tokens are transferred from the user to the contract.
5. User's share of the lending pool increases.
6. Tokens begin earning interest from borrower payments.

#### Borrowing Flow

1. The borrower must already have collateral posted in at least one OTHER cToken inside the same isolated market. Collateral posted in the BorrowableCToken being borrowed from is not eligible: a single account cannot simultaneously hold debt and collateral in the same cToken (`BorrowableCToken__CollateralPositionActive` reverts the borrow).
2. User calls `borrow()` specifying desired amounts.
3. Contract accrues pending interest and updates rates.
4. MarketManager validates borrowing capacity against collateral.
5. Debt is recorded with current debt index for interest calculations.
6. Underlying assets are transferred to the borrower.
7. Borrowing sets the account's `cooldownTimestamp`. During the 20-minute hold window, the Market Manager blocks repayment, transfers, and redeem or withdraw paths that call `canRedeemWithCollateralRemoval` or `canTransfer`. Deposits are not blocked by this hold period.

#### Repayment Flow

1. User calls `repay()` or `repayFor()`  with repayment amount.
2. Contract accrues pending interest to capture latest debt amount.
3. Interest is calculated based on time elapsed and current rates.
4. Underlying tokens are transferred from `msg.sender` (the payer) to the contract.
5. The cToken transfers underlying from `msg.sender`, then the Market Manager's `canRepayWithReview` hook validates that repayment is allowed before debt storage is updated. If the hook reverts, the whole transaction reverts.
6. The borrower's `_debtOf` is updated and `marketOutstandingDebt` is decreased.
7. Repayment does not notify or mutate Market Manager position state. `canRepayWithReview` is a validation hook: it checks the hold period, confirms the borrower has an active debt position for the cToken, and enforces the minimum remaining loan size unless the repayment fully clears the debt.

#### Interest Accrual Flow

1. \_accrueIfNeeded() is called before most operations.
2. Time elapsed since last accrual is calculated.
3. The packed borrow rate in `_vestingData` is used to accrue pending interest. The cToken calls `IRM.adjustedBorrowRate(...)` only when `block.timestamp >= vestingEnd`; otherwise accrual uses the stored rate and does not fetch a new rate from the DynamicIRM.
4. Interest is applied to all outstanding debt.
5. Interest increases the value of BorrowableCTokens.
6. Debt indices are updated for accurate individual debt tracking.

#### Liquidation Flow

1. Liquidator identifies underwater positions.
2. Calls liquidateExact() or liquidate() functions
3. Contract accrues pending interest to get current debt values.
4. MarketManager calculates liquidation amounts and collateral seizure.
5. Liquidator repays portion of debt.
6. Collateral is seized from borrower and transferred to liquidator.
7. Any bad debt is recognized and removed from total assets.

### Debt Index System

BorrowableCTokens use a sophisticated debt index system for accurate interest calculations:

* **Market Debt Index:** Global index that increases as interest accrues, stored in  the upper bits of `_vestingData`.
* **Account Debt Index:** Personal index recorded when debt is created or modified, stored with each user's debt amount.
* **Interest Calculation:** User's accrued interest = \
  **`(current market index / user's debt index - 1) * principal debt.`**

This system ensures accurate interest calculations regardless of when users borrow or repay.

### Liquidation Mechanics

BorrowableCTokens support comprehensive liquidation capabilities:&#x20;

* **Solvency Checks:** Borrow paths and collateral-removing redemption or transfer paths run Market Manager liquidity checks. Repayments do not recompute a collateral-to-debt solvency check; `canRepayWithReview` enforces the hold period, confirms an active debt position, and checks the minimum remaining loan size unless the repayment fully clears the debt. Liquidation eligibility is checked on-chain through `canLiquidate`. There is no protocol-internal off-chain monitor; liquidators discover underwater positions and submit liquidation transactions themselves.
* **Liquidation Triggers:** Positions become liquidatable when collateral value falls below required thresholds.
* **Partial Liquidations:** `liquidateExact(debtAmounts, accounts, collateralToken)` lets a liquidator specify per-account repayment amounts smaller than the maximum permitted; `liquidate(accounts, collateralToken)` liquidates each account up to the protocol-permitted maximum.
* **Bad Debt:** Independently of partial vs full liquidation, when the seized collateral cannot cover the debt being liquidated, the shortfall is recognized as bad debt and `_totalAssets` is reduced by that amount (lenders absorb the loss). A `BadDebtRecognized` event is emitted.
* **Incentive Structure:** Liquidators receive collateral bonuses for maintaining market health.

### Flash Loan Functionality

BorrowableCTokens provide flash loan capabilities for advanced users:

* **Uncollateralized Borrowing:** Temporary loans without collateral requirements.
* **Atomic Transactions:** Borrowed funds must be returned within the same transaction.
* **Fee Structure:** 0.04% fee (4 basis points) on borrowed amounts.
* **Callback Integration:** The borrower's contract (i.e. `msg.sender`) must implement `IFlashLoan.onFlashLoan(uint256 assets, uint256 assetsReturned, bytes data)`. The cToken does not implement `IFlashLoan`; it calls into the borrower's implementation during the loan.
* **Use Cases**: Arbitrage, liquidations, collateral swapping, and other DeFi strategies.&#x20;

### Position Manager Integration

BorrowableCTokens integrate with Position Managers for sophisticated strategies:

* **Leveraged Positions:** Automated borrowing and collateral posting for leverage.
* **Cross-Asset Operations:** Coordinated actions across multiple cTokens within the same isolated market (e.g. borrow `cUSDC` and post `cWETH` collateral atomically, against a `WETH | USDC` market).
* **Slippage Protection:** Position Manager entrypoints use `checkSlippage` as an oracle-priced pre/post sanity check. It is not a full replacement for user-side swap-route slippage controls; users choose the slippage parameter and should validate route-level execution before using leverage or deleverage operations.
* **Callback Mechanisms:** Position Managers can execute custom logic during borrow operations.

### Security Features

BorrowableCTokens implement multiple security layers:

1. **Debt-Collateral Separation:** An account cannot have debt and posted collateral in the same cToken. Borrowing from a BorrowableCToken reverts if the account has collateral posted in that same cToken, and posting collateral to a BorrowableCToken reverts if the account has debt in that same cToken. Accounts can still post one listed cToken as collateral while borrowing another listed cToken in the same isolated market.
2. **Pause Mechanisms:** Market Managers expose per-token `mintPaused`, `collateralizationPaused`, and `borrowPaused` flags through `actionsPaused(cToken)`, plus market-wide `liquidationPaused`, `redeemPaused`, and `transferPaused` flags.
3. **Reentrancy Protection:** Deposit, mint, withdraw, redeem, collateral, borrow, repay, liquidation, and accrual entrypoints are protected with `nonReentrant`. `flashLoan()` is not marked `nonReentrant`; it accrues before transferring assets, calls `msg.sender.onFlashLoan(...)`, then pulls back `assets + fee`.
4. **Interest Rate Limits:** Maximum bounds on interest rates to prevent exploitation.
5. **Oracle Integration:** Each priced asset is sourced through a single Chainlink-like adaptor configured per asset, with heartbeat and staleness checks on every read. Pegged assets (LSTs and stablecoins) carry an additional price-guard layer that constrains the reported price to a bounded range relative to a reference feed.
6. **Flash Loan Protection:** Borrowing or posting collateral starts a 20-minute hold window for the account. During this window the account cannot repay debt, transfer cToken shares, redeem, or withdraw through cToken paths that consult the Market Manager. Plain deposits are unaffected by the hold period.

### Protocol Revenue

BorrowableCTokens generate protocol revenue through multiple mechanisms:

* I**nterest Fees:** A configurable percentage (`interestFee`, max 60%, in BPS) of each interest accrual is captured by minting new BorrowableCToken shares directly to `centralRegistry.daoAddress()`. The fee is paid in shares, not in underlying, so the DAO's claim grows pari passu with lenders' claims.
* **Flash Loan Fees:** Charged in underlying. The 4-bp fee is added to `_totalAssets`, so the entire flash-loan fee accrues to lenders, not to the DAO.
* **Excess Recovery:** The DAO can call `skim()` to sweep excess underlying, such as rounding dust or donations, to `centralRegistry.daoAddress()`. `skimAvailable()` returns `marketOutstandingDebt + underlyingBalance - _totalAssets`, but reverts with `BaseCToken__ZeroAmount()` if there is no recoverable excess.

### Risk Management

**Debt Caps:** Maximum borrowing limits per market to control protocol exposure.

**Collateral Factors:** Loan-to-value ratios based on asset risk profiles.

**Liquidation Thresholds:** Dynamic thresholds that trigger liquidations before insolvency.

**Oracle Safeguards:** Curvance routes asset pricing through the Oracle Manager. In current live deployments, each priced asset uses a single Chainlink-like adaptor with heartbeat and staleness checks. Pegged or reference-priced assets can also use price guards that constrain reported prices to configured bounds.

**Interest Rate Bounds:** DynamicIRM bounds the annual base rate, annual vertex rate, vertex start, adjustment velocity, decay rate, and maximum vertex multiplier. It does not enforce a generic minimum borrow or supply rate.

### Integration Considerations

**State Updates:** Always call functions that update interest before relying on debt or balance information.

**Market Conditions:** Interest rates can change based on utilization and DynamicIRM vertex multiplier updates. Liquidation requirements, incentives, close factors, collateral caps, and debt caps are Market Manager token configuration, with optional transient liquidation values during auction-based liquidations; they are not utilization-driven thresholds.

## User Interaction Functions

### Borrow Functions

#### borrow()

**Contract:** BorrowableCToken.sol

**Description:** Borrows underlying tokens from lenders, based on collateral posted inside this market.

**Function signature:**

```solidity
function borrow(uint256 assets, address receiver) external nonReentrant
```

<table><thead><tr><th width="104">Type</th><th width="123">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assets</td><td>The amount of the underlying asset to borrow.</td></tr><tr><td>address</td><td>receiver</td><td>The address that will receive the borrowed assets.</td></tr></tbody></table>

**Event:**

```solidity
// From BorrowableCToken.
event Borrow(uint256 assets, uint256 debtAssetsOwed, address account);
// From the underlying ERC20 asset, emitted when borrowed assets are sent to `receiver`.
event Transfer(address indexed from, address indexed to, uint256 value);
```

```solidity
// From MarketManager, emitted when the account opens a debt position in this cToken.
event PositionUpdated(address cToken, address account, bool open);
```

***

#### borrowFor()

**Contract:** BorrowableCToken.sol

**Description:** Used by a delegated user to borrow underlying tokens from lenders, based on collateral posted inside this market by `account`

**Function signature:**

```solidity
function borrowFor(
    uint256 assets,
    address receiver,
    address owner
) external nonReentrant
```

<table><thead><tr><th width="125">Type</th><th width="127">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>amount</td><td>The amount of the underlying asset to borrow.</td></tr><tr><td>address</td><td>receiver</td><td>The account that will receive the borrowed assets.</td></tr><tr><td>address</td><td>owner</td><td>The account whose collateral backs the new debt. The caller must be approved as <code>owner</code>'s delegate.</td></tr></tbody></table>

**Event:**

```solidity
// From BorrowableCToken.
event Borrow(uint256 assets, uint256 debtAssetsOwed, address account);

// From the underlying ERC20 asset, emitted when borrowed assets are sent to `receiver`.
event Transfer(address indexed from, address indexed to, uint256 value);
```

```solidity
// From MarketManager, emitted when the owner opens a debt position in this cToken.
event PositionUpdated(address cToken, address account, bool open);
```

***

### Borrow Functions

#### flashLoan()

Contract: BorrowableCToken.sol

**Description:** Lends underlying assets to `msg.sender` for the duration of a single transaction. The borrower's contract must implement `IFlashLoan.onFlashLoan(uint256 assets, uint256 assetsReturned, bytes calldata data) external returns (bytes32)` and, before `flashLoan` returns, repay `assets + flashFee(assets)` to the cToken. The callback return value is ignored by the cToken. The fee is 4 bps (0.04%) and accrues to lenders by being added to `_totalAssets`. `flashLoan` checks the cToken's actual underlying ERC20 balance, not `assetsHeld()`, and reverts with `BorrowableCToken__InsufficientAssetsHeld()` if the requested amount exceeds that balance.

**Function signature:**

```solidity
function flashLoan(uint256 assets, bytes calldata data) external
```

<table><thead><tr><th width="99">Type</th><th width="107">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assets</td><td>The amount of <code>asset()</code> loaned during the flashloan.</td></tr><tr><td>bytes</td><td>data</td><td>Arbitrary calldata passed to flashloan callback to execute desired action during the flashloan.</td></tr></tbody></table>

**Event:**

```solidity
event Flashloan(uint256 assets, uint256 assetsFee, address account);
```

***

### Repay Functions

#### repay()

**Contract:** BorrowableCToken.sol

**Description:** Repays underlying tokens to lenders, freeing up their collateral posted inside this market and updates interest before executing the repayment.

**Function signature:**

```solidity
function repay(uint256 assets) external nonReentrant
```

<table><thead><tr><th width="99">Type</th><th width="100">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assets</td><td>The amount of the underlying asset to repay, or 0 for the full outstanding amount</td></tr></tbody></table>

**Event:**

```solidity
// From BorrowableCToken.
event Repay(uint256 assets, uint256 debtAssetsOwed, address payer, address account);

// From the underlying ERC20 asset, emitted when the payer transfers repayment assets to the cToken.
event Transfer(address indexed from, address indexed to, uint256 value);
```

```solidity
// From BorrowableCToken, emitted before `Repay` only if accrual work occurs.
event DebtAccrued(uint256 newDebtAssets, uint256 protocolFeeAssets);
event RatesAdjusted(uint256 debtPerSecond, uint256 nextAdjustment);
```

`repay()` does not emit `PositionUpdated`.

***

#### repayFor()

**Contract:** BorrowableCToken.sol

**Description:** Repays underlying tokens to lenders, on behalf of `account`, freeing up their collateral posted inside this market.

**Function signature:**

```solidity
function repayFor(uint256 assets, address owner) external nonReentrant
```

<table><thead><tr><th width="112">Type</th><th width="144">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assets</td><td>The amount to repay, or 0 for the full outstanding amount.</td></tr><tr><td>address</td><td>account</td><td>The account address to repay on behalf of. <code>repayFor</code> is not delegate-gated; any payer can repay debt for <code>owner</code>.</td></tr></tbody></table>

**Event:**

```solidity
// From BorrowableCToken.sol
event Repay(uint256 assets, uint256 debtAssetsOwed, address payer, address account);
// Only emitted if interest needs to be accrued.
// Emitted from underlying ERC20 asset transfer.
event Transfer(address indexed from, address indexed to, uint256 value);
```

```solidity
// From MarketManager
// Potentially emitted if a position is fully closed.
event PositionUpdated(address cToken, address account, bool open);
```

***

### Debt Tracking

#### debtBalance()

**Contract:** BorrowableCToken.sol

**Description:** Returns the current debt balance for a given account without accruing pending interest. This view uses the account's stored debt amount and debt index with the market debt index packed in `_vestingData`; it does not use the cToken exchange rate.

**Function signature:**

```solidity
function debtBalance(address account) public view returns (uint256 r)
```

<table><thead><tr><th width="112.54998779296875">Type</th><th width="113.88330078125">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>account</td><td>The address whose debt balance should be calculated</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="120.75003051757812">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The current balance of debt for the account</td></tr></tbody></table>

***

#### debtBalanceUpdated()

**Contract:** BorrowableCToken.sol

**Description:** Returns the current outstanding debt balance of an account with pending interest applied via `_accrueIfNeeded()`.

**Function signature:**

```solidity
    function debtBalanceUpdated(
        address account
    ) external nonReentrant returns (uint256 result)
```

<table><thead><tr><th width="115.2833251953125">Type</th><th width="144.26666259765625">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>account</td><td>The address whose debt balance should be calculated</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="134.39996337890625">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The current outstanding debt for <code>account</code> after accruing pending interest in the current transaction.</td></tr></tbody></table>

***

### Liquidation Functions

#### liquidate()

**Contract:** BorrowableCToken.sol&#x20;

**Description:** Liquidates each underwater account in `accounts` for as much debt as the protocol permits, transferring the seized collateral from `collateralToken` to the liquidator. Accrues pending interest before computing liquidation amounts. Reverts if `collateralToken == address(this)`, since debt and collateral cannot share a cToken. Any shortfall between seized collateral and debt is realized as bad debt and absorbed by lenders via a reduction in `_totalAssets`.&#x20;

**Function signature:**

```solidity
function liquidate(
    address[] calldata accounts,
    address collateralToken
) external nonReentrant
```

**Event:**

```solidity
// From BorrowableCToken.sol, emitted once per liquidated account.
event Repay(uint256 assets, uint256 debtAssetsOwed, address payer, address account);
// Only emitted if collateral was insufficient to cover the debt.
event BadDebtRecognized(uint256 assets, address liquidator);
// From the collateral cToken's `seize()`, emitted once per liquidated account.
event CollateralUpdated(uint256 shares, bool increased, address account);
event Liquidated(uint256 shares, address liquidator, address account);
event Transfer(address indexed from, address indexed to, uint256 value);
```

#### liquidateExact()

**Contract:** BorrowableCToken.sol&#x20;

**Description:** Liquidates underwater accounts by repaying exactly `debtAmounts[i]` of debt for `accounts[i]`. The two arrays must be the same length or the call reverts. The `accounts` array must be sorted in strictly ascending address order; `canLiquidate` reverts if any account is less than or equal to the previous account. Each exact amount must be no larger than the protocol-permitted maximum and must be backed by enough collateral shares for that exact liquidation. Use `liquidate()` when you want each account liquidated to the maximum permitted; use `liquidateExact()` when you need fine-grained control over per-account repayment, such as liquidating multiple accounts atomically with a fixed debt-token budget.

**Function signature:**

```solidity
function liquidateExact(
    uint256[] calldata debtAmounts,
    address[] calldata accounts,
    address collateralToken
) external nonReentrant
```

**Event:** Same as `liquidate()` above.

***

### Flash Loan Helpers

#### flashFee()

**Contract:** BorrowableCToken.sol&#x20;

**Description:** Returns the fee (in underlying asset units) that will be charged for a flash loan of `assets`. The fee is `assets * 4 / 10000` rounded up, and accrues entirely to lenders by being added to `_totalAssets` at the end of the loan. Pure view; safe to call off-chain or from inside other contracts.&#x20;

**Function signature:**

```solidity
function flashFee(uint256 assets) public pure returns (uint256 fee)
```

**Event:** None (pure function).

***

### Market State Views

#### assetsHeld()

**Contract:** BorrowableCToken.sol&#x20;

**Description:** Returns the amount of underlying currently available for borrows and ordinary withdrawals that check borrowable liquidity. Computed as `_totalAssets - marketOutstandingDebt - _BASE_UNDERLYING_RESERVE`, so it excludes the protocol's permanent reserve (77,777 wei of underlying) that backs the initial mint. Flash loans do not use `assetsHeld()`; `flashLoan` checks the cToken's actual underlying ERC20 balance. `assetsHeld()` reverts with `BorrowableCToken__DepositsNotInitialized()` if the market has not yet been started by the Market Manager.

**Function signature:**

```solidity
function assetsHeld() public view returns (uint256 result)
```

**Event:** None (view function).

#### marketOutstandingDebtUpdated()

**Contract:** BorrowableCToken.sol&#x20;

**Description:** Accrues any pending interest, then returns the current market-wide outstanding debt in underlying asset units. Use this when an integration needs an up-to-the-block debt total, e.g. for utilization or APY calculations. The non-accruing analog is the public state variable `marketOutstandingDebt`, which reads the last-accrued value without paying for the storage write.&#x20;

**Function signature:**

```solidity
function marketOutstandingDebtUpdated() external nonReentrant returns (uint256 result)
```

**Event:**

```solidity
// From BorrowableCToken.sol, emitted only if the IRM rolled into a new adjustment window.
event RatesAdjusted(uint256 debtPerSecond, uint256 nextAdjustment);
// Only emitted if interest was accrued.
event DebtAccrued(uint256 newDebtAssets, uint256 protocolFeeAssets);
```

#### isBorrowable()

**Contract:** BorrowableCToken.sol&#x20;

**Description:** Returns `true` for any contract inheriting from `BorrowableCToken`, and `false` for `SimpleCToken` and `StrategyCToken`. Useful for integrators that handle cToken addresses generically and need to dispatch on whether a token supports borrowing, debt views, or flash loans. Pure function; the result is fixed at deployment.&#x20;

**Function signature:**

```solidity
function isBorrowable() public pure override returns (bool result)
```

**Event:** None (pure function).

***


# Dynamic Interest Rate Model

### Overview

The Dynamic Interest Rate Model is a sophisticated interest rate mechanism designed to efficiently balance supply and demand within Curvance lending markets. It builds upon traditional "jump rate" models while introducing dynamic elements that automatically respond to changing market conditions. &#x20;

### Core Components

#### Interest Rate Structure

The model uses two interest-rate slopes and one utilization threshold:

* **Base Rate:** Applied linearly while utilization is at or below `vertexStart`.
* **Vertex Rate:** Applied only to utilization above `vertexStart`. This slope is multiplied by `vertexMultiplier`.
* **Vertex Start:** The utilization threshold where the model switches from the base slope to the vertex slope.

#### Dynamic Vertex Multiplier

The heart of the model is the Vertex Multiplier - a dynamic coefficient that adjusts based on market conditions:

* **Default Value:** Starts at 1.0 (WAD).
* **Maximum Value:** Capped at vertexMultiplierMax.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FksYCAuSaihHRB6L6byt8%2Fimage.png?alt=media&amp;token=1649ca23-8e9e-41ef-bb28-f25efb89de16" alt=""><figcaption></figcaption></figure>

### Data Flow

1. Market Utilization → Calculated as `(debt * WAD) / (assetsHeld + debt)`, or `0` when `debt == 0`.
2. Utilization → Drives interest rate calculations through base and vertex formulas.
3. Interest Rates → Applied to borrowers and distributed to lenders (minus protocol fees).
4. Vertex Multiplier → Dynamically adjusted when the linked BorrowableCToken calls `adjustedBorrowRate(...)`. Governance configuration updates can also reset it to `WAD` or clamp it to a lower `vertexMultiplierMax`. In normal cToken flow, `BorrowableCToken._accrueIfNeeded()` calls the IRM when `block.timestamp >= vestingEnd`, then uses the returned `ADJUSTMENT_RATE` to schedule the next vesting window.
5. Decay Mechanism → Applied lazily during multiplier updates. It reduces elevated multiplier values per adjustment and floors the result at `WAD`.

### State Machine

The Vertex Multiplier operates as a state machine with the following transitions:

#### States and Transitions

* Initialization State
  * **Starting point:** `vertexMultiplier = WAD` when the IRM is deployed or when governance updates the IRM with `vertexReset = true`.
  * **The IRM does not store a next-update timestamp.** The linked BorrowableCToken stores vesting timing and calls `adjustedBorrowRate(...)` when an accrual crosses the current vesting window.
* Adjustment States
  * Based on utilization relative to thresholds:
    * **At or below `decreaseThresholdEnd`:** maximum negative adjustment plus decay.
    * **Above `decreaseThresholdEnd` and at or below `vertexStart`:** scaled negative adjustment plus decay. At exactly `vertexStart`, the scaled negative adjustment is zero, so only decay applies.
    * **Above `vertexStart` and at or below `increaseThresholdStart`:** decay only.
    * **Above `increaseThresholdStart`:** positive adjustment plus decay.
* Transition Conditions
  * Updates occur when the linked token calls the model; the model returns ADJUSTMENT\_RATE as the cadence hint.
  * Rate update only possible when properly linked to a BorrowableCToken.

#### Decay Mechanism

The decay feature introduces a downward sloping characteristic:

* When the multiplier is elevated, a constant negative velocity is applied.
* This occurs regardless of positive/negative acceleration from utilization.
  * If the multiplier is elevated due to high utilization, it naturally decays over time to prevent interest rates from remaining too high indefinitely.
  * Decay subtracts a fraction of the current multiplier each adjustment, pulling it downward toward 1.0 (floored at WAD).
* Creates natural incentives for borrowers while protecting against liquidity crunches.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FpJ6zLgS0hI5PyMwgnPVO%2FCopy%20of%20Line%20graph(1).png?alt=media&amp;token=be319327-74f8-4f90-adf7-627cd3eb6fa4" alt=""><figcaption></figcaption></figure>

### Configuration Parameters

The model is governed by several parameters that define its behavior:

* **Base Parameters:**
  * **`baseRatePerSecond`:** Stored per-second base slope, in WAD, used when utilization is at or below `vertexStart`.
  * **`vertexRatePerSecond`:** Stored per-second vertex slope, in WAD, applied only to utilization above `vertexStart` and multiplied by `vertexMultiplier`.
  * **`vertexStart`:** Utilization point, stored in WAD, where the model switches from the base slope to the vertex slope.
* **Adjustment Controls:**
  * **ADJUSTMENT\_RATE:** The fixed 10-minute interval returned to the linked cToken for scheduling vesting windows. The IRM does not run on its own timer; updates happen when accrual calls cross a vesting window.
  * **adjustmentVelocity:** Maximum rate of multiplier change per update.
  * **vertexMultiplierMax:** Maximum allowed value for multiplier.
* **Threshold Parameters:**
  * **`increaseThresholdStart`:** Utilization point above `vertexStart` where positive multiplier adjustments begin. It is derived as halfway between `vertexStart` and 100% utilization.
  * **`decreaseThresholdEnd`:** Utilization point below `vertexStart` where negative multiplier adjustments max out. An elevated multiplier starts decreasing as soon as utilization is at or below `vertexStart`.
  * **`decayPerAdjustment`:** Rate, in BPS, used to compute `decay = multiplier * decayPerAdjustment / BPS` on each multiplier update. The resulting `decay` amount is subtracted from the multiplier.

### Design Benefits

1. **Responsive to Market Conditions:**
   1. High utilization leads to increased rates, attracting lenders.
   2. Sustained high rates encourage borrowers to repay.
2. S**elf-Balancing:**
   1. Creates a feedback loop that stabilizes market liquidity.
   2. Raises rates as utilization remains elevated, but it does not guarantee that liquidity crunches cannot occur.
3. **Growth Incentives:**
   1. The decay mechanism can lower an elevated vertex multiplier over later adjustment windows.
   2. Stable markets can trend back toward the base multiplier as accrual-triggered updates apply decay.
4. **Gas Optimization:**
   1. `DynamicIRM` stores `vertexMultiplier` as a standalone `uint256` and stores configuration in `ratesConfig`. If you mention bit packing, scope it to the linked cToken's `_vestingData`, which packs vesting rate, vesting end, last vesting claim, and debt index.
   2. Efficient math calculations for model computation.

### Practical Example

If a market experiences sustained high utilization:

1. Interest rates will gradually increase as the Vertex Multiplier rises.
2. This attracts new lenders while encouraging borrowers to repay.
3. As utilization decreases, rates begin to fall (but not immediately due to the multiplier).
4. The decay mechanism can help rates normalize when utilization is low enough for decay to dominate any positive multiplier adjustment. If utilization remains above `increaseThresholdStart`, the positive adjustment can keep the multiplier elevated or push it higher, subject to `vertexMultiplierMax`.

This creates a more stable, responsive system compared to fixed-rate models while protecting the protocol from potential liquidity crises.

***

## Math Equations

**Constants:**

* **WAD** = 1e18
* **BPS** = 10\_000
* **WAD\_BPS** = 1e22

1. **Utilization Rate Calculation**

$$
Utilization Rate = Debt / (AssetsHeld + Debt)
$$

```solidity
    function utilizationRate(
        uint256 assetsHeld,
        uint256 debt
    ) public pure returns (uint256 r) {
        // Utilization rate is 0 when there are no outstanding debt.
        r = debt == 0 ? 0 : _mulDiv(debt, WAD, assetsHeld + debt);
    }
```

***

2. **Base Interest Rate Calculation**

$$
Base Rate = (Utilization \* baseRatePerSecond) / WAD
$$

```solidity
    function _baseRate(
        uint256 util,
        uint256 baseRatePerSecond
    ) internal pure returns (uint256 r) {
        r = _mulDiv(util, baseRatePerSecond, WAD);
    }
```

***

3. **Vertex Interest Rate Calculation**

$$
VertexInterestRate =
(vertexStart \* baseRatePerSecond) / WAD
\+ ((util - vertexStart) \* vertexRatePerSecond \* vertexMultiplier) / WAD^2
$$

```solidity
    function _vertexRate(
        uint256 util,
        uint256 baseRatePerSecond,
        uint256 vertexRatePerSecond,
        uint256 vertexStart,
        uint256 multiplier
    ) internal pure returns (uint256 r) {
        r = _mulDiv(vertexStart, baseRatePerSecond, WAD) +
            _mulDiv(
            util - vertexStart,
            vertexRatePerSecond * multiplier,
            WAD_SQUARED
        );
    }
```

***

4. **Final Borrow Interest Rate Calculation**

**If `util <= vertexStart`:**&#x20;

$$
BorrowRate = (util \* baseRatePerSecond) / WAD
$$

**If `util > vertexStart`:**

$$
BorrowRate =
(vertexStart \* baseRatePerSecond) / WAD
\+ ((util - vertexStart) \* vertexRatePerSecond \* vertexMultiplier) / WAD^2
$$

```solidity
    function borrowRate(
        uint256 assetsHeld,
        uint256 debt
    ) public view returns (uint256 r) {
        uint256 util = utilizationRate(assetsHeld, debt);
        // Cache from storage since we only need to query a new config values.
        RatesConfig storage c = ratesConfig;
        uint256 vertexStart = c.vertexStart;

        if (util <= vertexStart) {
            return _baseRate(util, c.baseRatePerSecond);
        }

        r = _vertexRate(
            util,
            c.baseRatePerSecond,
            c.vertexRatePerSecond,
            vertexStart,
            vertexMultiplier
        );
    }
```

***

5. **Vertex Multiplier Adjustment (Above Vertex)**

**decay:**

$$decay = (multiplier \* decayPerAdjustment) / BPS$$

**start:**&#x20;

$$start = decreaseThresholdEnd \* INFLECTIONDENOMINATION$$

**Case 1: Low Utilization (utilization <= start)**

$$
newMultiplier = max((multiplier - decay), WAD)
$$

**Case 2: High Utilization (utilization > start)**

$$
shift = ((util - start ) \* WAD) / (WAD - start)
$$

$$
adjustedShift = WADBPS+ (shift \* adjustmentVelocity)
$$

$$
newMultiplier = (multiplier \* adjustedShift) / WADBPS - decay
$$

$$
finalResult = min(max(newMultiplier, WAD), vertexMultiplierMax)
$$

```solidity
    function _updateAboveVertex(
        RatesConfig memory c,
        uint256 util,
        uint256 multiplier
    ) internal pure returns (uint256 newMultiplier) {
        // Calculate decay rate.
        uint256 decay = _mulDiv(multiplier, c.decayPerAdjustment, BPS);

        if (util <= (_INFLECTION_DENOMINATION * uint256(c.increaseThresholdStart))) {
            newMultiplier = multiplier - decay;

            // Check if decay rate sends new rate below 1.
            return newMultiplier < WAD ? WAD : newMultiplier;
        }

        // Apply a positive multiplier to the current multiplier based on
        // `util` vs `increaseThresholdStart` and `WAD`.
        // Then apply decay effect.
        newMultiplier = _positiveShift(
            multiplier, // `multiplier` in `WAD`.
            c.adjustmentVelocity, // `adjustmentVelocity` in `BPS`.
            decay, // `decay` in `multiplier` aka `WAD`.
            util, // `current` in `WAD`.
            _INFLECTION_DENOMINATION * uint256(c.increaseThresholdStart) // `start` convert to WAD to match util.
        );

        // Update and return with adjustment and decay rate applied.
        // Its theorectically possible for the multiplier to be below 1
        // due to decay, so we need to check like in below vertex.
        if (newMultiplier < WAD) {
            return WAD;
        }

        // Make sure `newMultiplier` is not above `vertexMultiplierMax`.
        newMultiplier = newMultiplier < c.vertexMultiplierMax
            ? newMultiplier
            : c.vertexMultiplierMax;
    }
```

```solidity
    function _positiveShift(
        uint256 multiplier,
        uint256 adjustmentVelocity,
        uint256 decay,
        uint256 current, // `util`.
        uint256 start // `increaseThresholdStart`.
    ) internal pure returns (uint256 result) {
        // We do not need to check for current >= end, since we know util is
        // the absolute maximum utilization is 100%, and thus current == end.
        // Which will result in WAD result for `shift`.
        // Thus, this will be bound between [0, WAD].
        uint256 shift = _mulDiv(current - start, WAD, WAD - start);

        // Apply `shift` result to adjustment velocity.
        // Then add 100% on top for final adjustment value to `multiplier`.
        // We use WAD_BPS here since `shift` is in `WAD` for max precision but
        // `adjustmentVelocity` is in BPS since it does not need greater
        // precision due to being configured in `BPS`.
        shift = WAD_BPS + (shift * adjustmentVelocity);

        // Apply positive `shift` effect to `currentMultiplier`, and
        // adjust for 1e36 precision. Then apply decay effect.
        result = _mulDiv(multiplier, shift, WAD_BPS) -  decay;
    }
```

***

6. **Vertex Multiplier Adjustment (Below Vertex)**

**decay:**

$$decay = (multiplier \* decayPerAdjustment) / BPS$$

**end:**

$$end = decreaseThresholdEnd \* INFLECTIONDENOMINATION$$

**Case 1: Very Low Utilization (utilization <= end)**

$$
newMultiplier = max((multiplier \* BPS) / (BPS + adjustmentVelocity) - decay, WAD)
$$

**Case 2: Moderate Utilization (utilization > end)**

$$
shift = ((vertexStart - util) \* WAD / (vertexStart - end)
$$

$$
adjustedShift = WADBPS + (shift \* adjustmentVelocity)
$$

$$
newMultiplier = (multiplier \* WADBPS) / adjustedShift - decay
$$

$$
result = max(newMultiplier, WAD)
$$

```solidity
    function _updateBelowVertex(
        RatesConfig memory c,
        uint256 util,
        uint256 multiplier
    ) internal pure returns (uint256 newMultiplier) {
        // Calculate decay rate.
        uint256 decay = _mulDiv(multiplier, c.decayPerAdjustment, BPS);

        // Convert `decreaseThresholdEnd` to `WAD` to be in same terms as
        // `util`, no precision loss as a result.
        if (util <= (_INFLECTION_DENOMINATION * uint256(c.decreaseThresholdEnd))) {
            // Apply maximum adjustVelocity reduction (shift = 1).
            // We only need to adjust for `BPS` precision since `shift`
            // is not used here.
            // currentMultiplier / (1 + adjustmentVelocity) = newMultiplier.
            newMultiplier = _mulDiv(
                multiplier,
                BPS,
                BPS + c.adjustmentVelocity
            ) - decay;

            // Check if decay rate sends multiplier below 1.
            return newMultiplier < WAD ? WAD : newMultiplier;
        }

        // Apply a negative multiplier to the current multiplier based on
        // `util` vs `vertexStart` and `decreaseThresholdEnd`.
        // Then apply decay effect.
        newMultiplier = _negativeShift(
            multiplier, // `multiplier` in `WAD`.
            c.adjustmentVelocity, // `adjustmentVelocity` in `BPS`.
            decay, // `decay` in `multiplier` aka `WAD`.
            util, // `current` in `WAD`.
            c.vertexStart, // `start` in `WAD`.
            _INFLECTION_DENOMINATION * uint256(c.decreaseThresholdEnd) // `end` convert to WAD to match util/vertexStart.
        );

        // Update and return with adjustment and decay rate applied.
        // But first check if new rate sends multiplier below 1.
        return newMultiplier < WAD ? WAD : newMultiplier;
    }
```

```solidity
    function _negativeShift(
        uint256 multiplier,
        uint256 adjustmentVelocity,
        uint256 decay,
        uint256 current, // `util`.
        uint256 start, // `vertexStart`.
        uint256 end // `decreaseThresholdEnd`.
    ) internal pure returns (uint256 result) {
        // Calculate linear curve multiplier. We know that current > end,
        // based on pre conditional checks.
        // Thus, this will be bound between [0, WAD].
        uint256 shift = _mulDiv(start - current, WAD, start - end);

        // Apply `shift` result to adjustment velocity.
        // Then add 100% on top for final adjustment value to `multiplier`.
        // We use WAD_BPS here since `shift` is in `WAD` for max precision but
        // `adjustmentVelocity` is in BPS since it does not need greater
        // precision due to being configured in `BPS`.
        shift = WAD_BPS + (shift * adjustmentVelocity);

        // Apply negative `shift` effect to `currentMultiplier`, and
        // adjust for 1e36 precision. Then apply decay effect.
        result = _mulDiv(multiplier, WAD_BPS, shift) -  decay;
    }
```

***

## User-Facing View Functions

#### predictedBorrowRate()

**Contract:** DynamicIRM

**Description:** Calculates the current borrow rate per second with updated vertex multiplier applied, predicting what the rate will be after the next adjustment.

**Function signature:**

```solidity
function predictedBorrowRate(
    uint256 assetsHeld,
    uint256 debt
    ) public view returns (uint256 result)
```

<table><thead><tr><th width="129">Type</th><th width="173">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assetsHeld</td><td>The amount of underlying assets held in the pool.</td></tr><tr><td>uint256</td><td>debt</td><td>The amount of outstanding debt in the pool.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="147">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The borrow rate percentage per second, in <code>WAD</code>.</td></tr></tbody></table>

***

#### borrowRate()

**Contract:** DynamicIRM

**Description:** Calculates the current borrow rate per second based on current market conditions.

**Function signature:**

<pre class="language-solidity"><code class="lang-solidity">function borrowRate(
<strong>    uint256 assetsHeld,
</strong>    uint256 debt
) public view returns (uint256 r)
</code></pre>

<table><thead><tr><th width="124">Type</th><th width="181">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assetsHeld</td><td>The amount of underlying assets held in the pool.</td></tr><tr><td>uint256</td><td>debt</td><td>The amount of outstanding debt in the pool.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="137">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The borrow interest rate percentage, per second, in <code>WAD</code>.</td></tr></tbody></table>

***

#### supplyRate()

**Contract:** DynamicIRM

**Description:** Calculates the current supply rate per second for frontend data querying. It should not be used for on-chain execution.

**Function signature:**

```solidity
function supplyRate(
    uint256 assetsHeld,
    uint256 debt,
    uint256 interestFee
) public view returns (uint256 r)
```

<table><thead><tr><th width="124">Type</th><th width="181">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assetsHeld</td><td>The amount of underlying assets held in the pool.</td></tr><tr><td>uint256</td><td>debt</td><td>The amount of outstanding debt in the pool.</td></tr><tr><td>uint256</td><td>interestFee</td><td>The current interest rate protocol fee for the market token, in <code>BPS</code>.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="137">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The supply interest rate percentage, per second, in <code>WAD</code>.</td></tr></tbody></table>

***

#### utilizationRate()

**Contract:** DynamicIRM

**Description:** Calculates market utilization as `(debt * WAD) / (assetsHeld + debt)`. Returns `0` when `debt == 0`.&#x20;

**Function signature:**

```solidity
function utilizationRate(
    uint256 assetsHeld,
    uint256 debt
) public pure returns (uint256 r)
```

<table><thead><tr><th width="124">Type</th><th width="181">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assetsHeld</td><td>The amount of underlying assets held in the pool.</td></tr><tr><td>uint256</td><td>debt</td><td>The amount of outstanding debt in the pool.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="137">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The utilization rate between [0, WAD].</td></tr></tbody></table>

***

#### vertexMultiplier()

**Contract:** DynamicIRM

**Description:** Public getter for the dynamic value applied to `vertexRatePerSecond`, in `WAD`. The multiplier is dynamically adjusted by `adjustedBorrowRate(...)`, can be reset or clamped by governance configuration updates, is floored at `WAD`, and is capped by `vertexMultiplierMax`.

**Function signature:**

```solidity
// uint256 public vertexMultiplier;
function vertexMultiplier() public view returns (uint256);
```

**Return data:**

<table><thead><tr><th width="137">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The current vertex multiplier, in <code>WAD</code>.</td></tr></tbody></table>

***

#### linkedToken()

**Contract:** DynamicIRM

**Description:** The borrowable Curvance token linked to this interest rate&#x20;model contract.

**Function signature:**

```solidity
function linkedToken() external view returns (address result);
```

**Return data:**

<table><thead><tr><th width="137">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The linked borrowableCToken address.</td></tr></tbody></table>

***


# Universal Balance

## Overview

The Universal Balance system is a user-facing token management layer within the Curvance Protocol, providing a flexible interface for users to manage their assets. It enables seamless transitions between idle holdings and yield-generating positions within Curvance's ecosystem, all while maintaining non-custodial control.

The system consists of two main contracts:

* **UniversalBalance:** Core implementation for ERC20 tokens.
* **UniversalBalanceNative:** Extension that adds support for native gas tokens (ETH, BERA, etc.).

### Core Concepts

#### Dual Balance System

Universal Balance introduces a dual-balance accounting model for each user:

1. **Sitting Balance:** Tokens held in the contract but not deployed to lending markets.
2. **Lent Balance:** Tokens deployed into Curvance's lending markets to earn yield.

This dual-state approach allows users to maintain instant liquidity for a portion of their funds while earning yield on another portion, all through a single interface.

### Architecture

#### Core Components

* **UniversalBalance:** Base implementation for ERC20 tokens.
* **UniversalBalanceNative:** Extension for native gas tokens with wrapping/unwrapping.
* **BorrowableCToken:** Corresponding lending market token that generates yield.
* **CentralRegistry:** Provides system-wide configuration and permissions.

#### State Management

Universal Balance maintains the following key state variables:

```solidity
struct UserBalance {
    uint256 sittingBalance;
    uint256 lentBalance;
}

// User balances tracking
mapping(address => UserBalance) public userBalances;

// Contract configuration
IBorrowableCToken public immutable linkedToken;
address public immutable underlying;
```

Each Universal Balance contract is linked to:

1. A specific underlying token (e.g., USDC).
2. The corresponding BorrowableCToken in Curvance (e.g., cUSDC).

### Data Flows

#### Deposit Flow

1. User deposits tokens to UniversalBalance via `deposit()` or `depositFor()` .
2. User specifies whether to keep as sitting balance or lend it.
3. If lending is chosen:
   1. Tokens are transferred to the BorrowableCToken contract via `deposit()` .
   2. Resulting BorrowableCToken shares are tracked in the user's `lentBalance` .
4. If keeping as sitting balance:
   1. Tokens are held in the `UniversalBalance` contract.
   2. User's `sittingBalance` is increased.

#### Withdrawal Flow

1. User requests withdrawal via `withdraw()` or `withdrawFor()` .
2. Contract checks if withdrawal can be fulfilled from sitting balance.
3. If sitting balance is insufficient:
   1. Required BorrowableCTokens are redeemed from lending market.
   2. Underlying tokens are received.
4. Tokens are sent to the recipient.
5. User's balance (sitting or lent) is reduced accordingly.

#### Balance Conversion Flow

1. User requests to lend sitting balance via `lendAssets()` .
   1. Sitting balance is reduced.
   2. Tokens are deployed to lending market.
   3. Lent balance is increased.
2. User requests to unlend via `unlendAssets()` .
   1. Lent balance is reduced.
   2. BorrowableCTokens are redeemed from lending market.
   3. Sitting balance is increased.

#### Native Token Flow (UniversalBalanceNative)

1. User sends native tokens (ETH) directly to contract.
   1. Native tokens are wrapped (WETH).
   2. Added to user's sitting balance.
2. User deposits native tokens via `depositNative()` .
   1. Similar to standard deposit with auto-wrapping.
3. User withdraws to native tokens via `withdrawNative()` .
   1. Wrapped tokens are unwrapped.
   2. Native tokens are sent to recipient.

### Integration with Curvance Ecosystem

#### BorrowableCToken Interaction

* Universal Balance contracts interact directly with their linked BorrowableCToken contracts.
* When lending, they call `mint()` on the BorrowableCToken.
* When unlending, they call `redeem()` on the BorrowableCToken.
* Yield accrues automatically through the BorrowableCToken's interest model.

#### Oracle Integration

UniversalBalanceNative includes special support for oracle funding:

1. Oracle Manager can request funds via `useBalanceForOracleUpdate()` .
2. User's balance is used to pay for oracle updates.
3. This enables "pull-based" oracle updates where users can fund oracle operations.

### Security Features

1. **Permission System:** Implements delegated operations through the `PluginDelegable`  contract.
2. **Reentrancy Protection:** All critical functions include reentrancy guards.
3. **Non-Custodial Design:** Users maintain full control of their assets.
4. **Permission Checks:** Actions that affect user balances verify permissions.
5. **Batch Operations:** Multi-user functions to reduce gas costs and transaction volume.

### User Features

#### Social Elements

* **Direct Transfers:** Transfer portions of balance to other users.
* **Permission Delegation:** Allow third-party operations on your balance.
* **Batch Operations:** Efficient multi-user management.

#### Flexibility

* **Dynamic Allocation:** Freely shift between sitting and lent states.
* **Multiple Recipients:** Deposit or withdraw to different addresses.
* **Mixed Source Withdrawals:** Pull from sitting first, then lent as needed.

### Implementation Notes

* Each UniversalBalance contract is token-specific, managing only one underlying asset.
* Deposit and withdrawal functions include options for immediate lending.
* The system tracks balances via accounting rather than transferring tokens to users.
* For native token operations, wrapping/unwrapping occurs transparently to the user.

***

## User Interaction Functions

### UniversalBalance Functions

#### deposit()

**Description:** Deposits underlying token into user's Universal Balance account, either to be held or lent out.

**Contract:** UniversalBalance.sol

**Function signature:**

```solidity
function deposit(uint256 amount, bool willLend) external
```

<table><thead><tr><th width="103">Type</th><th width="111">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>amount</td><td>The amount of underlying token to be deposited.</td></tr><tr><td>bool</td><td>willLend</td><td>Whether the deposited underlying tokens should be lent out inside Curvance Protocol.</td></tr></tbody></table>

**Events:**

```solidity
// From UniversalBalance
event Deposit(
    address indexed by,
    address indexed owner,
    uint256 assets,
    bool lendingDeposit
)
```

```solidity
// From Underlying Asset & BorrowableCToken
// Potentially emitted multiple times:
// 1. Always emitted when assets are transferred to UniversalBalance
// 2. If willLend is true, transferring underlying asset from UniversalBalance to BorrowableCToken
// 3. Emitted when BorrowableCTokens are minted.
event Transfer(address indexed from, address indexed to, uint256 value);
```

***

#### depositFor()

**Description:** Deposits underlying token into recipient's Universal Balance account, either to be held or lent out. Requires that recipient has approved the caller previously to access their Universal Balance.

**Contract:** UniversalBalance.sol

**Function signature:**

```solidity
function depositFor(uint256 amount, bool willLend, address recipient) external
```

<table><thead><tr><th width="112">Type</th><th width="126">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>amount</td><td>The amount of underlying token to be deposited.</td></tr><tr><td>bool</td><td>willLend</td><td>Whether the deposited underlying tokens should be lent out inside Curvance Protocol.</td></tr><tr><td>address</td><td>recipient</td><td>The account who will receive the deposit.</td></tr></tbody></table>

**Events:**

```solidity
// From UniversalBalance
event Deposit(
    address indexed by,
    address indexed owner,
    uint256 assets,
    bool lendingDeposit
)
```

```solidity
// From Underlying Asset & BorrowableCToken
// Potentially emitted multiple times:
// 1. Always emitted when assets are transferred to UniversalBalance
// 2. If willLend is true, transferring underlying asset from UniversalBalance to BorrowableCToken
// 3. Emitted when BorrowableCTokens are minted.
event Transfer(address indexed from, address indexed to, uint256 value);
```

***

#### multiDepositFor()

**Description:** Deposits underlying token into recipients Universal Balance accounts, either to be held or lent out. Requires that all recipients have approved the caller previously to access their Universal Balance.

**Contract:** UniversalBalance.sol

**Function signature:**

```solidity
function multiDepositFor(
    uint256 depositSum,
    uint256[] calldata amounts,
    bool[] calldata willLend,
    address[] calldata recipients
) external
```

<table><thead><tr><th width="116">Type</th><th width="125">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>depositSum</td><td>The total sum of underlying tokens being deposited.</td></tr><tr><td>uint256[]</td><td>amounts</td><td>An array containing the amount of underlying token to be deposited to each account.</td></tr><tr><td>bool[]</td><td>willLend</td><td>An array containing whether the deposited underlying tokens should be lent out inside Curvance Protocol for each account.</td></tr><tr><td>address[]</td><td>recipients</td><td>An array containing the accounts who will receive a deposit based on their matching amounts value.</td></tr></tbody></table>

**Events:**

For each deposit:

```solidity
// From UniversalBalance
event Deposit(
    address indexed by,
    address indexed owner,
    uint256 assets,
    bool lendingDeposit
)
```

```solidity
// From Underlying Asset & BorrowableCToken
// Potentially emitted multiple times:
// 1. Always emitted when assets are transferred to UniversalBalance
// 2. If willLend is true, transferring underlying asset from UniversalBalance to BorrowableCTokens
// 3. Emitted when BorrowableCTokens are minted.
event Transfer(address indexed from, address indexed to, uint256 value);
```

***

#### withdraw()

**Description:** Withdraws underlying token from user's Universal Balance account, currently held or lent out.

**Contract:** UniversalBalance.sol

**Function signature:**

```solidity
function withdraw(
    uint256 amount,
    bool forceLentRedemption,
    address recipient
) external returns (uint256 amountWithdrawn, bool lendingBalanceUsed)
```

<table><thead><tr><th width="95">Type</th><th width="195">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>amount</td><td>The amount of underlying token to be withdrawn.</td></tr><tr><td>bool</td><td>forceLentRedemption</td><td>Whether the withdrawn underlying tokens should be pulled only from owner's lent position or the full account.</td></tr><tr><td>address</td><td>recipient</td><td>The account who will receive the underlying assets.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="104">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The amount of underlying token actually withdrawn.</td></tr><tr><td>bool</td><td>Whether lent balance was used for the withdrawal.</td></tr></tbody></table>

**Events:**

```solidity
// From UniversalBalance
event Withdraw(
    address indexed by,
    address indexed to,
    address indexed owner,
    uint256 assets,
    bool lendingRedemption
)
```

```solidity
// From BorrowableCToken & Underlying asset.
// Always emitted from underlying asset when transferring to the recipient.
// If withdrawing from lent balance:
// Emitted from the cToken when burning tokens during redemption.
// Emitted from the underlying asset when transferring from cToken to Universal Balance.
event Transfer(address indexed from, address indexed to, uint256 value);
```

<pre class="language-solidity"><code class="lang-solidity">// From MarketManager
<strong>// Potentially emitted if positions need to be closed.
</strong>event PositionUpdated(address cToken, address account, bool open);
</code></pre>

***

withdrawFor

**Description:** Withdraws underlying token from owner's Universal Balance account, currently held or lent out. Requires that owner has approved the caller previously to access their Universal Balance.

**Contract:** UniversalBalance

**Function signature:**

```solidity
function withdrawFor(
    uint256 amount,
    bool forceLentRedemption,
    address recipient,
    address owner
) external returns (uint256 amountWithdrawn, bool lendingBalanceUsed)
```

<table><thead><tr><th width="109">Type</th><th width="196">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>amount</td><td>The amount of underlying token to be withdrawn.</td></tr><tr><td>bool</td><td>forceLentRedemption</td><td>Whether the withdrawn underlying tokens should be pulled only from owner's lent position or the full account.</td></tr><tr><td>address</td><td>recipient</td><td>The account who will receive the underlying assets.</td></tr><tr><td>address</td><td>owner</td><td>The account that will redeem from their universal balance.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="122.75006103515625">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The amount of underlying token actually withdrawn.</td></tr><tr><td>bool</td><td>Whether lent balance was used for the withdrawal..</td></tr></tbody></table>

**Events:**

```solidity
event Withdraw(
    address indexed by,
    address indexed to,
    address indexed owner,
    uint256 assets,
    bool lendingRedemption
)
```

```solidity
// From cToken & Underlying asset.
// Always emitted from underlying asset when transferring to the recipient.
// If withdrawing from lent balance:
// Emitted from the cToken when burning tokens during redemption.
// Emitted from the underlying asset when transferring from cToken to Universal Balance.
event Transfer(address indexed from, address indexed to, uint256 value);
```

<pre class="language-solidity"><code class="lang-solidity">// From MarketManager
<strong>// Potentially emitted if positions need to be closed.
</strong>event PositionUpdated(address cToken, address account, bool open);
</code></pre>

***

#### multiWithdrawFor

**Description:** Withdraws underlying token from owners Universal Balance accounts, currently held or lent out. Requires that each owners has approved the caller previously to access their Universal Balance.

**Contract:** UniversalBalance.sol

**Function signature:**

```solidity
function multiWithdrawFor(
    uint256[] calldata amounts,
    bool[] calldata forceLentRedemption,
    address recipient,
    address[] calldata owners
) external
```

<table><thead><tr><th width="113">Type</th><th width="218">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256[]</td><td>amounts</td><td>An array containing the amount of underlying token to be withdrawn from each account.</td></tr><tr><td>bool[]</td><td>forceLentRedemption</td><td>An array containing whether the withdrawn underlying tokens should be pulled only from an owners lent position or the full account.</td></tr><tr><td>address</td><td>recipient</td><td>The account who will receive the underlying assets.</td></tr><tr><td>address[]</td><td>owners</td><td>An array containing the accounts that will redeem from their Universal Balance.</td></tr></tbody></table>

**Events:**

For each withdrawal:

```solidity
event Withdraw(
    address indexed by,
    address indexed to,
    address indexed owner,
    uint256 assets,
    bool lendingRedemption
)
```

```solidity
// From cToken & Underlying asset.
// Always emitted from underlying asset when transferring to the recipient.
// If withdrawing from lent balance:
// Emitted from the cToken when burning tokens during redemption.
// Emitted from the underlying asset when transferring from EToken to Universal Balance.
event Transfer(address indexed from, address indexed to, uint256 value);
```

<pre class="language-solidity"><code class="lang-solidity">// From MarketManager
<strong>// Potentially emitted if positions need to be closed.
</strong>event PositionUpdated(address cToken, address account, bool open);
</code></pre>

***

#### shiftBalance()

**Description:** Moves a user's Universal Balance between lent and sitting mode.

**Contract:** UniversalBalance.sol

**Function signature:**

```solidity
function shiftBalance(
    uint256 amount,
    bool fromLent
) external returns (uint256 amountWithdrawn, bool lendingBalanceUsed)
```

<table><thead><tr><th width="108">Type</th><th width="114">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>amount</td><td>The amount of underlying token to be shifted.</td></tr><tr><td>bool</td><td>fromLent</td><td>Whether the shifted underlying tokens should be pulled from the user's lent balance or the sitting balance.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="115">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The amount of underlying token actually shifted</td></tr><tr><td>bool</td><td>Whether lent balance was used for the operation</td></tr></tbody></table>

**Events:**

```solidity
// From UniversalBalance
event Withdraw(
    address indexed by,
    address indexed to,
    address indexed owner,
    uint256 assets,
    bool lendingRedemption
)

event Deposit(
    address indexed by,
    address indexed owner,
    uint256 assets,
    bool lendingDeposit
)
```

```solidity
// 1. Emitted when burning tokens during redemption if shifting from lent balance
// 2. Emitted from the underlying token when transferring from cToken to UniversalBalance
// 3. Emitted from underlying token is transferred from UniversalBalance to the Etoken if willLend is true
event Transfer(address indexed from, address indexed to, uint256 value);
// Potentially emitted from MarketManager if positions need to be closed
event PositionUpdated(address cToken, address account, bool open);
```

***

#### transfer()

**Description:** Transfers amount from caller's Universal Balance, currently held or lent out to recipient.

**Contract:** UniversalBalance.sol

**Function signature:**

```solidity
function transfer(
    uint256 amount,
    bool forceLentRedemption,
    bool willLend,
    address recipient
) external returns (uint256 amountTransferred, bool lendingBalanceUsed)
```

<table><thead><tr><th width="105">Type</th><th width="205">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>amount</td><td>The amount of underlying token to be transferred.</td></tr><tr><td>bool</td><td>forceLentRedemption</td><td>Whether the transferred underlying tokens should be pulled only from the caller's lent position or the full account.</td></tr><tr><td>bool</td><td>willLend</td><td>Whether the deposited underlying tokens should be lent out inside Curvance Protocol.</td></tr><tr><td>address</td><td>recipient</td><td>The account who will receive the transferred balance.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="107">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The amount of underlying token actually transferred.</td></tr><tr><td>bool</td><td>Whether lent balance was used for the transfer.</td></tr></tbody></table>

**Events:**

<pre class="language-solidity"><code class="lang-solidity"><strong>// From UniversalBalance
</strong><strong>event Withdraw(
</strong>    address indexed by,
    address indexed to,
    address indexed owner,
    uint256 assets,
    bool lendingRedemption
)

event Deposit(
    address indexed by,
    address indexed owner,
    uint256 assets,
    bool lendingDeposit
)
</code></pre>

```solidity
// 1. Emitted when burning tokens during redemption if shifting from lent balance
// 2. Emitted from the underlying token when transferring from EToken to UniversalBalance
// 3. Emitted from underlying token is transferred from UniversalBalance to the cToken if willLend is true
event Transfer(address indexed from, address indexed to, uint256 value);
// Potentially emitted from MarketManager if positions need to be closed
event PositionUpdated(address mToken, address account, bool open);
```

***

#### transferFor()

**Description:** Transfers amount from owner's Universal Balance, currently held or lent out to recipient. Requires that owner has approved the caller previously to access their Universal Balance.

**Contract:** UniversalBalance.sol

**Function signature:**

```solidity
function transferFor(
    uint256 amount,
    bool forceLentRedemption,
    bool willLend,
    address recipient,
    address owner
) external returns (uint256 amountTransferred, bool lendingBalanceUsed)
```

<table><thead><tr><th width="103">Type</th><th width="199">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>amount</td><td>The amount of underlying token to be transferred.</td></tr><tr><td>bool</td><td>forceLentRedemption</td><td>Whether the transferred underlying tokens should be pulled only from owner's lent position or the full account.</td></tr><tr><td>bool</td><td>willLend</td><td>Whether the deposited underlying tokens should be lent out inside Curvance Protocol.</td></tr><tr><td>address</td><td>recipient</td><td>The account who will receive the transferred balance.</td></tr><tr><td>address</td><td>owner</td><td>The account that will transfer from their universal balance.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="108">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The amount of underlying token actually transferred.</td></tr><tr><td>bool</td><td>Whether lent balance was used for the transfer.</td></tr></tbody></table>

**Events:**

```solidity
// From UniversalBalance
event Withdraw(
    address indexed by,
    address indexed to,
    address indexed owner,
    uint256 assets,
    bool lendingRedemption
)

event Deposit(
    address indexed by,
    address indexed owner,
    uint256 assets,
    bool lendingDeposit
)
```

```solidity
// From EToken
// Only emitted if interest needs to be accrued.
event InterestAccrued(
    uint256 debtAccumulated,
    uint256 exchangeRate,
    uint256 totalBorrows
);
// 1. Emitted when burning tokens during redemption if shifting from lent balance
// 2. Emitted from the underlying token when transferring from cToken to UniversalBalance
// 3. Emitted from underlying token is transferred from UniversalBalance to the cToken if willLend is true
event Transfer(address indexed from, address indexed to, uint256 value);
// Potentially emitted from MarketManager if positions need to be closed
event PositionUpdated(address mToken, address account, bool open);
```

***

### UniversalBalanceNative Functions

#### depositNative()

**Description:** Deposits native gas token into user's Universal Balance account, either to be held or lent out.

**Contract:** UniversalBalanceNative.sol

**Function signature:**

```solidity
function depositNative(bool isLent) external payable
```

<table><thead><tr><th width="85">Type</th><th width="105">Name</th><th>Description</th></tr></thead><tbody><tr><td>bool</td><td>isLent</td><td>Whether the deposited native tokens should be lent out inside Curvance Protocol (as wrapped native).</td></tr></tbody></table>

**Events:**

```solidity
// From UniversalBalanceNative
event Deposit(
    address indexed by,
    address indexed owner,
    uint256 assets,
    bool lendingDeposit
)
// Possibly emitted from the wrapped native asset
event Deposit(address from, address to, uint256 value);
```

```solidity
// From EToken
// 1. Emitted if isLent is true
// 2. Emitted from the cToken when tokens are minted to UniversalBalance
event Transfer(address indexed from, address indexed to, uint256 value);
```

***

#### depositNativeFor()

**Description:** Deposits native gas token into recipient's Universal Balance account, either to be held or lent out. Requires that recipient has approved the caller previously to access their Universal Balance.

**Contract:** UniversalBalanceNative.sol

**Function signature:**

```solidity
function depositNativeFor(
    bool isLent,
    address recipient
) external payable
```

<table><thead><tr><th width="115">Type</th><th width="107">Name</th><th>Description</th></tr></thead><tbody><tr><td>bool</td><td>isLent</td><td>Whether the deposited native tokens should be lent out inside Curvance Protocol (as wrapped native).</td></tr><tr><td>address</td><td>recipient</td><td>The account who will receive the deposit.</td></tr></tbody></table>

**Events:**

```solidity
// From UniversalBalanceNative
event Deposit(
    address indexed by,
    address indexed owner,
    uint256 assets,
    bool lendingDeposit
)
// Possibly emitted from the wrapped native asset
event Deposit(address from, address to, uint256 value);
```

```solidity
// From BorrowableCToken
// 1. Emitted if isLent is true
// 2. Emitted from the BorrowableCToken when tokens are minted to UniversalBalance
event Transfer(address indexed from, address indexed to, uint256 value);
// Only emitted if interest needs to be accrued.
event InterestAccrued(
    uint256 debtAccumulated,
    uint256 exchangeRate,
    uint256 totalBorrows
);
```

***

#### multiDepositNativeFor()

**Description:** Deposits native gas token into recipient's Universal Balance account, either to be held or lent out. Requires that all recipients have approved the caller previously to access their Universal Balance.

**Contract:** UniversalBalanceNative.sol

**Function signature:**

```solidity
function multiDepositNativeFor(
    uint256[] calldata amounts,
    bool[] calldata willLend,
    address[] calldata recipients
) external payable
```

<table><thead><tr><th width="121">Type</th><th width="119">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256[]</td><td>amounts</td><td>An array containing the amount of native token to be deposited to each account.</td></tr><tr><td>bool[]</td><td>willLend</td><td>An array containing whether the deposited native tokens should be lent out inside Curvance Protocol for each account.</td></tr><tr><td>address[]</td><td>recipients</td><td>An array containing the accounts who will receive a deposit based on their matching amounts value.</td></tr></tbody></table>

**Events:**

```solidity
// From UniversalBalanceNative
event Deposit(
    address indexed by,
    address indexed owner,
    uint256 assets,
    bool lendingDeposit
)
// Possibly emitted from the wrapped native asset
event Deposit(address from, address to, uint256 value);
```

```solidity
// From BorrowableCToken
// 1. Emitted if isLent is true
// 2. Emitted from the BorrowableCToken when tokens are minted to UniversalBalance
event Transfer(address indexed from, address indexed to, uint256 value);
```

***

#### withdrawNative()

**Description:** Withdraws wrapped native token from user's Universal Balance account, either currently held or lent out and transfers it to the user in native form.

**Contract:** UniversalBalanceNative.sol

**Function signature:**

```solidity
function withdrawNative(
    uint256 amount,
    bool forceLentRedemption,
    address recipient
) external returns (uint256 amountWithdrawn, bool lendingBalanceUsed)
```

<table><thead><tr><th width="108">Type</th><th width="202">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>amount</td><td>The amount of native token to be withdrawn.</td></tr><tr><td>bool</td><td>forceLentRedemption</td><td>Whether the withdrawn underlying tokens should be pulled only from owner's lent position or the full account.</td></tr><tr><td>address</td><td>recipient</td><td>The account who will receive the underlying assets.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="100">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The amount of native token actually withdrawn.</td></tr><tr><td>bool</td><td>Whether lent balance was used for the withdrawal.</td></tr></tbody></table>

**Events:**

<pre class="language-solidity"><code class="lang-solidity"><strong>// From UniversalBalanceNative
</strong><strong>event Withdraw(
</strong>    address indexed by,
    address indexed to,
    address indexed owner,
    uint256 assets,
    bool lendingRedemption
)
// From the wrapped native asset
event Withdrawal(address user, uint256 amount);
</code></pre>

```solidity
// From BorrowableCToken
// 1. Emitted when burning tokens during redemption.
// 2. Emitted from the wrapped native asset.
event Transfer(address indexed from, address indexed to, uint256 value);
```

```solidity
// From MarketManager
// Potentially emitted if positions need to be closed.
event PositionAdjusted(address mToken, address account, bool open);
```

***

#### withdrawNativeFor()

**Description:** Withdraws wrapped native token from owner's universal balance account, either currently held or lent out and transfers it to the user in native form. Requires that owner has approved the caller previously to access their Universal Balance.

**Contract:** UniversalBalanceNative.sol

**Function signature:**

```solidity
function withdrawNativeFor(
    uint256 amount,
    bool forceLentRedemption,
    address recipient,
    address owner
) external returns (uint256 amountWithdrawn, bool lendingBalanceUsed)
```

<table><thead><tr><th width="113">Type</th><th width="205">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>amount</td><td>The amount of native token to be withdrawn.</td></tr><tr><td>bool</td><td>forceLentRedemption</td><td>Whether the withdrawn underlying tokens should be pulled only from owner's lent position or the full account.</td></tr><tr><td>address</td><td>recipient</td><td>The account who will receive the native token.</td></tr><tr><td>address</td><td>owner</td><td>The account that will redeem from their universal balance.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="117">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The amount of native token actually withdrawn.</td></tr><tr><td>bool</td><td>Whether lent balance was used for the withdrawal.</td></tr></tbody></table>

**Events:**

<pre class="language-solidity"><code class="lang-solidity"><strong>// From UniversalBalanceNative
</strong><strong>event Withdraw(
</strong>    address indexed by,
    address indexed to,
    address indexed owner,
    uint256 assets,
    bool lendingRedemption
)
// Possibly from the wrapped native asset
event Withdrawal(address user, uint256 amount);
</code></pre>

```solidity
// From BorrowableCToken
// 1. Emitted when burning tokens during redemption.
// 2. Emitted from the wrapped native asset.
event Transfer(address indexed from, address indexed to, uint256 value);
```

```solidity
// From MarketManager
// Potentially emitted if positions need to be closed.
event PositionUpdated(address cToken, address account, bool open);
```

***

#### multiWithdrawNativeFor()

**Description:** Withdraws native gas token from owners Universal Balance accounts, currently held or lent out. Requires that each owners has approved the caller previously to access their Universal Balance.

**Contract:** UniversalBalanceNative.sol

**Function signature:**

```solidity
function multiWithdrawNativeFor(
    uint256[] calldata amounts,
    bool[] calldata forceLentRedemption,
    address recipient,
    address[] calldata owners
) external
```

<table><thead><tr><th width="113">Type</th><th width="188">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256[]</td><td>amounts</td><td>An array containing the amount of native token to be withdrawn from each account.</td></tr><tr><td>bool[]</td><td>forceLentRedemption</td><td>An array containing whether the withdrawn underlying tokens should be pulled only from an owners lent position or the full account.</td></tr><tr><td>address</td><td>recipient</td><td>The account who will receive the native assets.</td></tr><tr><td>address[]</td><td>owners</td><td>An array containing the accounts that will redeem from their Universal Balance.</td></tr></tbody></table>

**Events:**

For every withdrawal:

<pre class="language-solidity"><code class="lang-solidity"><strong>// From UniversalBalanceNative
</strong><strong>event Withdraw(
</strong>    address indexed by,
    address indexed to,
    address indexed owner,
    uint256 assets,
    bool lendingRedemption
)
// Possibly from the wrapped native asset
event Withdrawal(address user, uint256 amount);
</code></pre>

```solidity
// From BorrowableCToken
// 1. Emitted when burning tokens during redemption.
// 2. Emitted from the wrapped native asset.
event Transfer(address indexed from, address indexed to, uint256 value);
```

```solidity
// From MarketManager
// Potentially emitted if positions need to be closed.
event PositionUpdated(address cToken, address account, bool open);
```


# Position Management

### Overview

Curvance Position Managers are router-style contracts that wrap leverage and deleverage actions inside an isolated lending market. They let users borrow, swap, deposit as collateral, withdraw collateral, swap, and repay through one PositionManager call while the linked cTokens and MarketManager continue to enforce listing, borrowability, collateral caps, debt caps, account liquidity, and repayment rules.

### Architecture Design

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FatQW1lhOe1XNUyxWOCdg%2Fimage.png?alt=media&amp;token=712e4342-9d5f-493e-a0cc-1e9e694fe70f" alt=""><figcaption></figcaption></figure>

### Core Components

#### PositionManagementBase

`BasePositionManager` is the abstract base contract for PositionManager implementations. It provides the shared external entrypoints, callback handling, delegation checks, reentrancy protection, pre/post account-value slippage checks, protocol leverage/deleverage fee handling, and common validation. Concrete managers implement the asset-specific leverage and deleverage conversion hooks.

Shared entrypoints include `depositAndLeverage`, `leverage`, `leverageFor`, `deleverage`, and `deleverageFor`.

#### Specialized Implementations

Each implementation extends the base functionality for a specific asset or protocol path:

* `SimplePositionManager`: generic non-native ERC20 leverage and deleverage.
* `SingleSidedVaultPositionManager`: ERC4626-style vault positions that enter the vault on leverage and use the simple deleverage path.
* `DualSidedVaultPositionManager`: vault positions that can redeem vault shares during deleverage before optionally swapping into the debt asset.
* `NativeVaultPositionManager`: native-token vault positions that unwrap wrapped native assets or swap into native assets before depositing.
* `PendlePTPositionManager`: Pendle principal token positions.

### Key Data Structures

#### LeverageStruct

```solidity
struct LeverageAction {
    IBorrowableCToken borrowableCToken;
    uint256 borrowAssets;
    ICToken cToken;
    uint256 expectedShares;
    SwapperLib.Swap swapAction;
    bytes auxData;
}
```

Encapsulates all data needed for a leverage operation, including which token to borrow, how much to borrow, and which position token to leverage against.

#### DeleverageStruct

```solidity
struct DeleverageStruct {
    ICToken cToken;
    uint256 collateralAssets;
    IBorrowableCToken borrowableCToken;
    uint256 repayAssets;
    SwapperLib.Swap[] swapAction;
    bytes auxData;
}
```

Contains data for deleverage operations, specifying which collateral to withdraw, which debt market to repay, the minimum debt-asset amount the PositionManager must hold after the deleverage route, and the swap or protocol-specific exit instructions. `repayAssets` is a floor, not always the final repayment amount. After the route runs, the PositionManager reads fresh debt and repays `min(assetsHeld, currentDebt)`.

### Integration Points

Position Managers interact with these Curvance components:

* **`MarketManager`:** authorizes PositionManager contracts, checks token listing, debt caps, collateral caps, account liquidity, repayment review, and redemption review.
* **`CentralRegistry`:** provides protocol-wide configuration, including `protocolLeverageFee()` and approved external calldata checkers.
* **cTokens and `BorrowableCToken`:** live cTokens are implemented through `BorrowableCToken`; they are not two separate live role classes. A cToken can receive deposits and can be posted as collateral when market configuration allows it. A cToken is borrowable in a specific market only when `marketManager.debtCaps(cToken) > 0`. PositionManager flows call `borrowForPositionManager` on the borrowed cToken and `withdrawByPositionManager` on the collateral cToken, then cToken and MarketManager logic perform the terminal checks.
* **`SwapperLib`:** executes approved external swap calldata and checks swap value through oracle-priced slippage.
* **External protocols:** specific PositionManager implementations handle Pendle, ERC4626-style vaults, and native vaults.

### Security Features

The PositionManager architecture combines several safety checks:

* Swap-level checks through `SwapperLib._swapSafe`, including approved calldata checkers and oracle-priced value-loss checks.
* Position-level sanity checks through `BasePositionManager.checkSlippage`, which compares the account's collateral-minus-debt value before and after the action.
* Route-specific floors such as `expectedShares` on leverage and `repayAssets` on deleverage.
* Market-level constraints enforced by cTokens and the `MarketManager`, including listing checks, borrow pauses, debt caps, collateral caps, account liquidity, hold-period checks, and repayment/redemption review.
* A protocol leverage/deleverage fee taken from the borrowed or withdrawn assets before the manager-specific route runs.
* Reentrancy protection on the PositionManager entrypoints and the relevant cToken entrypoints.

There is no standalone global max leverage ratio inside `BasePositionManager`. The practical ceiling comes from market configuration, caps, available liquidity, prices, fees, and route slippage.

### Protocol Interactions

**During leverage**, the user calls `depositAndLeverage`, `leverage`, or `leverageFor`. The PositionManager checks pre/post account value with `checkSlippage`, calls `BorrowableCToken.borrowForPositionManager`, receives the borrowed assets in the callback, applies the protocol leverage fee, runs the manager-specific swap or protocol entry logic, deposits the resulting asset into `action.cToken` as collateral for the account, enforces `expectedShares`, and then the borrowable cToken performs the terminal `MarketManager.canBorrow(...)` check.

**During deleverage**, the user calls `deleverage` or `deleverageFor`. The PositionManager checks pre/post account value with `checkSlippage`, verifies `repayAssets != 0`, calls `cToken.withdrawByPositionManager`, receives withdrawn collateral in the callback, applies the protocol leverage/deleverage fee, runs the manager-specific exit or swap logic, requires that the PositionManager holds at least `repayAssets` of the debt asset, repays up to the account's fresh debt balance, and refunds leftover debt, collateral, and swap-output balances to the owner.

This architecture enables Curvance to support complex leverage strategies across diverse asset types while maintaining protocol security and risk parameters.


# Leverage

### Overview

Leveraged positions in Curvance allow users to increase their exposure to certain assets by borrowing additional capital against their collateral. This documentation explains the core mechanics, data flows, and components involved in creating leveraged positions.

### Core Concepts

#### Position Types

Curvance supports leverage through concrete PositionManager implementations:

* **`SimplePositionManager`:** generic non-native ERC20 positions, such as major or stablecoin assets.
* **`PendlePTPositionManager`:** Pendle principal-token positions.
* **`SingleSidedVaultPositionManager`, `DualSidedVaultPositionManager`, `NativeVaultPositionManager:`**&#x76;ault and native-vault position paths.

#### Key Components

The leverage system involves several interconnected components:

* **`BasePositionManager`:** shared entrypoints, callback handling, delegation checks, reentrancy protection, pre/post account-value slippage checks, and protocol fee handling.
* **Concrete PositionManagers:** asset-specific hooks that convert the borrowed asset into the collateral-side asset or protocol receipt token.
* **`MarketManager`:** market listing, PositionManager authorization, collateral caps, debt caps, account liquidity, borrow pauses, and terminal borrow validation.
* **cTokens and `BorrowableCToken`:** live cTokens share the same underlying implementation family. A cToken can receive deposits and collateral, and it is borrowable in a market only when `marketManager.debtCaps(cToken) > 0`.
* **`SwapperLib` and `CentralRegistry`:** approved external swap calldata, swap-level value checks, and protocol configuration such as `protocolLeverageFee()`.

### Leverage Flow

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FnJLTYF2fhHFll22kIKwR%2Fimage.png?alt=media&amp;token=a55b0a3a-50e5-4373-86fc-1c2263f62f67" alt="" width="188"><figcaption></figcaption></figure>

#### 1. Initial Deposit and Request Flow

For a new position, `depositAndLeverage(assets, action, slippage)` pulls the initial underlying asset from the caller, calls `action.cToken.depositAsCollateral(assets, msg.sender)`, and then executes the leverage leg.

For an existing position, `leverage(action, slippage)` adds debt and collateral without a fresh initial deposit. The account still needs enough posted collateral in the same isolated market for the terminal borrow check to pass.

For delegated execution, `leverageFor(action, account, slippage)` requires the `account` to approve the caller on the PositionManager contract being used.

#### 3. Validation & Maximum Borrowing

There is no public `_maxRemainingLeverageOf(account, borrowToken)` function in the protocol contracts. For pre-trade sizing, integrators can use `ProtocolReader.hypotheticalLeverageOf(account, cToken, borrowableCToken, assets, bufferTime)`, which returns the current leverage, theoretical max leverage, adjusted max leverage, maximum debt amount, loan-size error flag, and oracle-error flag.

Execution is still enforced by the contracts. `BasePositionManager` calls `borrowForPositionManager`, the borrowed cToken checks that the caller is an enabled PositionManager, accrues interest, notifies the hold-period cooldown, borrows to the PositionManager, runs the callback, and then calls `marketManager.canBorrow(...)` for the terminal position. Debt caps, borrow pauses, account liquidity, and available cToken liquidity can still make the transaction revert.

```solidity
maxBorrowAmount = _maxRemainingLeverageOf(account, borrowToken)
```

#### 4. Token Borrowing

The system borrows tokens from the specified BorrowableCToken through a callback pattern:

```solidity
action.borrowableCToken.borrowForPositionManager(
    action.borrowAssets,
    account,
    action
);
```

#### 5. Asset Conversion

Depending on the token type, different specialized swapping mechanisms are used:

* **Simple Tokens:** Direct swaps through external DEXs.
* **Vault Tokens:** If a collateral cToken is a vault, and the borrowed cToken is a simple ERC20, the manager converts the borrowed asset into the vault deposit asset, deposits into the vault, and then deposits the minted vault receipt tokens into `action.cToken` as collateral. If the borrowed asset already matches the vault underlying, the swap is skipped. Native vaults unwrap `wrappedNative`, or swap into the native token, before depositing.
* **Pendle PT Tokens:** validates the supplied Pendle market, optionally pre-swaps the borrowed asset into `pendleAction.input.tokenIn`, and uses the Pendle Router to mint PT tokens. The resulting PT balance is deposited into `action.cToken` as collateral.

Each implementation handles the specific logic required for that token type.

### Asset-Specific Flows

#### Simple Token Leverage

Simple ERC20 leverage uses `SimplePositionManager`. The borrowed asset and target collateral asset must be different, and the manager requires one external swap whose `inputToken` is the debt asset, `outputToken` is `action.cToken.asset()`, and `inputAmount` equals the post-fee `action.borrowAssets`. The resulting collateral asset balance is deposited into `action.cToken` as collateral for the owner.

#### Vault Token Leverage

Vault token leverage is handled by vault-specific PositionManagers. The leverage path depends on whether the vault deposit asset is an ERC20 underlying or the chain's native token.

#### Pendle PT Token Leverage

Pendle PT leverage uses `PendlePTPositionManager`. `auxData` decodes as `(address lpToken, uint256 minPtAmount, PendleLib.PendleAction pendleAction)`. If a Curvance external swap is provided, its output must match `pendleAction.input.tokenIn`, and the manager enters the PT path through `PendleLib._enterPendle(..., true, ...)`.

### Protocol Fees

When leveraging, the base callback applies `centralRegistry.protocolLeverageFee()` to the borrowed amount before the manager-specific conversion path runs:

```solidity
uint256 fee = FixedPointMathLib.mulDivUp(
    actionAssets,
    centralRegistry.protocolLeverageFee(),
    BPS
);
```

The fee is BPS-denominated, not WAD-denominated, and it is transferred to `centralRegistry.daoAddress()`. For the simple ERC20 path, `swapAction.inputAmount` must match the net post-fee amount, because `SimplePositionManager` compares `swapAction.inputAmount` against the mutated `action.borrowAssets`.

### Slippage Protection

Leverage has several slippage and sanity checks:

* `checkSlippage(account, slippage)` compares the account's collateral-minus-debt value before and after the action using `marketManager.statusOf(account)`. The contract comments describe this as a sanity check, not the user's only slippage protection.
* `SwapperLib._swapSafe` validates the external swap target through the Central Registry calldata checker, enforces the calldata checker's minimum output, and compares oracle-priced input and output value against `swapAction.slippage`.
* `action.expectedShares` is enforced after the converted collateral asset is deposited into `action.cToken`.

### Security Considerations

#### Position Monitoring

Leveraged positions increase risk exposure and should be monitored closely for:

* Price movements that could trigger liquidations.
* Changes in market conditions affecting collateral value.
* Available liquidity for potential deleveraging.

#### Permission Management

Delegated leverage is checked on the PositionManager being called. `leverageFor(action, account, slippage)` calls `_checkDelegate(account, msg.sender)`, so the account must approve the caller on that PositionManager with `setDelegateApproval(delegate, true)`.

Users can revoke that PositionManager-specific approval with `setDelegateApproval(delegate, false)`. They can also mass-revoke prior delegate approvals across Curvance contracts by calling `centralRegistry.incrementApprovalIndex()`.

### Integration Points

When integrating with the leverage system, consider:

* Providing clear slippage parameters to protect users.
* Understanding the specific asset type and its leverage implementation.
* Ensuring sufficient collateral to avoid immediate liquidation risk.
* Monitoring gas costs, which vary based on the complexity of the leverage operation.

By understanding these core mechanics, developers can effectively integrate with and extend Curvance's leveraged position capabilities.

***

## User Interaction Functions

### Leverage Functions

#### depositAndLeverage()

**Description:** Deposits underlying assets into `action.cToken` as collateral and then leverages the position to increase both collateral and debt. Includes the `checkSlippage` sanity check. The caller must ERC20-approve the PositionManager to pull the initial underlying `assets`; cToken delegate approval is not required for this pre-deposit path because `depositAsCollateral` explicitly allows enabled PositionManager contracts to deposit for the receiver.

**Contract:** PositionManagement

**Function signature:**

```solidity
function depositAndLeverage(
    uint256 assets,
    LeverageAction calldata action,
    uint256 slippage
) external;
```

<table><thead><tr><th width="176">Type</th><th width="153">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>assets</td><td>The amount of the underlying assets to deposit.</td></tr><tr><td>LeverageStruct</td><td>leverageData</td><td>Structure containing leverage operation details including borrow token, amount, position token, swap data, and auxiliary data.</td></tr><tr><td>uint256</td><td>slippage</td><td>Slippage accepted by the user for the leverage action, in WAD (1e18).</td></tr></tbody></table>

**Events:**

```solidity
// PositionManager entrypoints do not emit a dedicated leverage event.

// ERC20 share and underlying transfers can emit Transfer.
event Transfer(address indexed from, address indexed to, uint256 value);
// PositionManager entrypoints do not emit a dedicated leverage event.

// ERC20 share and underlying transfers can emit Transfer.
event Transfer(address indexed from, address indexed to, uint256 value);

// cToken deposit path, inherited from ERC4626 and emitted by BaseCToken.
event Deposit(address indexed by, address indexed owner, uint256 assets, uint256 shares);

// Collateral posting/removal on BaseCToken.
event CollateralUpdated(uint256 shares, bool increased, address account);

// Borrow path on BorrowableCToken.
event Borrow(uint256 assets, uint256 debtAssetsOwed, address account);

// MarketManager position index updates, emitted when collateral or debt is first added
// to the account's active positions in this market.
event PositionUpdated(address cToken, address account, bool open);
// cToken deposit path, inherited from ERC4626 and emitted by BaseCToken.
event Deposit(address indexed by, address indexed owner, uint256 assets, uint256 shares);
```

***

#### leverage()

**Description:** Leverages an existing Curvance position to increase both collateral and debt. Includes slippage protection through the checkSlippage modifier.

**Contract:** PositionManagement

**Function signature:**

```solidity
function leverage(
    LeverageAction calldata action,
    uint256 slippage
) external
```

<table><thead><tr><th width="169">Type</th><th width="163">Name</th><th>Description</th></tr></thead><tbody><tr><td>LeverageStruct</td><td>leverageData</td><td>Structure containing leverage operation details including borrow token, amount, position token, swap data, and auxiliary data.</td></tr><tr><td>uint256</td><td>slippage</td><td>Slippage accepted by the user for the leverage action, in WAD (1e18).</td></tr></tbody></table>

**Events:**

```solidity
// In CToken.sol
event Transfer(address indexed from, address indexed to, uint256 value);
event Borrow(address account, uint256 amount);
```

```solidity
// Only emitted if this is the user's first position in the specific token
event PositionUpdated(address cToken, address account);
```

***

#### leverageFor()

**Description:** Leverages an existing Curvance position on behalf of another account via delegation. Includes slippage protection through the checkSlippage modifier. Requires delegation approval from the account being leveraged for.

**Contract:** PositionManagement

**Function signature:**

```solidity
function leverageFor(
    LeverageAction calldata action,
    address account,
    uint256 slippage
) external
```

<table><thead><tr><th width="176">Type</th><th width="160">Name</th><th>Description</th></tr></thead><tbody><tr><td>LeverageStruct</td><td>leverageData</td><td>Structure containing leverage operation details including borrow token, amount, position token, swap data, and auxiliary data.</td></tr><tr><td>address</td><td>account</td><td>The account to leverage an active Curvance position for.</td></tr><tr><td>uint256</td><td>slippage</td><td>Slippage accepted by the user for the leverage action, in WAD (1e18).</td></tr></tbody></table>

**Events:**

```solidity
// In CToken.sol
event Transfer(address indexed from, address indexed to, uint256 value);
event Borrow(address account, uint256 amount);
```

```solidity
// In BaseCToken.sol
event CollateralUpdated(uint256 shares, bool increased, address account);
```


# Deleverage / Fold

### Overview

Curvance PositionManagers are router-style contracts that wrap multi-step leverage and deleverage actions inside a single isolated market. For deleveraging, the PositionManager withdraws collateral-side assets, runs the manager-specific exit or swap path, repays debt, and returns defined leftover balances while the cTokens and MarketManager enforce market listing, PositionManager authorization, repayment rules, redemption rules, account liquidity, and cooldown checks.

### Key Components

The deleveraging flow involves:

* `BasePositionManager`: shared `deleverage` and `deleverageFor` entrypoints, callback handling, delegation checks, reentrancy protection, protocol fee handling, and pre/post account-value slippage checks.
* Concrete PositionManagers: asset-specific conversion logic for simple ERC20 routes, vault routes, Pendle routes, Velodrome routes, and Aerodrome routes.
* `MarketManager`: isolated-market listing, PositionManager authorization, account-status checks, repayment review, redemption review, pause state, caps, and cooldown enforcement.
* cTokens and `BorrowableCToken`: the collateral-side cToken executes `withdrawByPositionManager`, and the debt-side borrowable cToken receives repayment through `repayFor`. Live cTokens are not separate "collateral cToken" and "borrowable cToken" contract types; borrowability is a per-market configuration controlled by `debtCaps(cToken)`.

### Deleverage Process Flow

The position unwinding flow follows these key steps:

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2F2uYX4RrKTTLGmjUYDKZ7%2Fimage.png?alt=media&amp;token=ec11f655-e214-4651-b5ab-781877aef7be" alt="" width="563"><figcaption></figcaption></figure>

#### Data Flow

1. **Initiation:**\
   The position unwinding flow starts when the user calls `deleverage(action, slippage)` for their own account, or an approved delegate calls `deleverageFor(action, account, slippage)`.\
   \
   The `action` argument is a `DeleverageAction` containing:
   1. `cToken`: the cToken to withdraw from.
   2. `collateralAssets`: the amount of that cToken's underlying asset to redeem, not cToken shares.
   3. `borrowableCToken`: the debt-side cToken to repay.
   4. `repayAssets`: the minimum debt-asset balance the PositionManager must hold after the route.
   5. `swapActions`: manager-specific swap instructions.
   6. `auxData`: manager-specific auxiliary data.
2. **Position Token Withdrawal:**

   The PositionManager validates the action and calls `action.cToken.withdrawByPositionManager(action.collateralAssets, account, action)`. The cToken accrues, computes the shares needed with `previewWithdraw` logic, withdraws the underlying asset to the PositionManager, and then calls `onRedeem(...)` so the PositionManager can run the deleverage callback.
3. **Collateral Conversion:**

   Collateral conversion is implemented by the concrete PositionManager's `_swapCollateralAssetToDebtAsset(action)` hook.\
   \
   The exact route is manager-specific. `SimplePositionManager` requires exactly one collateral-asset-to-debt-asset swap. Vault managers may redeem vault shares first and can skip the external swap when the vault output already matches the debt asset. Pendle and Velodrome or Aerodrome managers exit their protocol position first, then optionally run one or more swaps whose endpoints must match the manager's expected output and final debt asset.
4. **Debt Repayment:**

   Debt repayment happens in the base deleverage callback after the manager-specific route finishes. The PositionManager checks that it holds at least `action.repayAssets` of the debt asset. It then reads the owner's fresh debt with `debtBalanceUpdated(owner)` and repays `min(assetsHeld, currentDebt)` through `borrowableCToken.repayFor(repayAssets, owner)`.<br>

   `repayAssets` is a minimum debt-asset output floor for the deleverage route. It is not always the exact final repayment amount, and setting it to zero reverts in the PositionManager.
5. **Asset Return:**

   After repayment, the base callback returns the balances it explicitly sweeps:

   * remaining debt asset;
   * remaining withdrawn collateral asset;
   * remaining balances of each `swapActions[i].outputToken`.

   Do not describe this as a blanket refund of every possible intermediate token. PositionManagers are router-style contracts and should not be treated as user-specific custody for arbitrary stranded balances.

### State Transitions

During deleverage, validation happens across the PositionManager, the collateral cToken, the debt cToken, and the MarketManager:

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FcfHZEKKGqcn8hWdlEOMJ%2Fimage.png?alt=media&amp;token=8dece441-793a-477a-a2c9-48d29bc9cc1c" alt="" width="563"><figcaption></figcaption></figure>

* `deleverageFor` checks delegation on the PositionManager contract.
* `_deleverage` rejects `repayAssets == 0` and requires `action.cToken` to be listed in the connected MarketManager.
* `withdrawByPositionManager` requires the caller to be an enabled PositionManager, accrues the cToken, computes the shares needed for `collateralAssets`, withdraws the underlying to the PositionManager, and runs the `onRedeem` callback.
* `onRedeem` requires the debt-side `borrowableCToken` to be listed and verifies that the callback token and amount match the action.
* `repayFor` accrues debt, checks the repayment through `MarketManager.canRepayWithReview(...)`, and can revert during the hold period or if a partial repay would leave debt below `MIN_LOAN_SIZE`.
* The final redemption review runs through `MarketManager.canRedeemWithCollateralRemoval(...)`, which enforces redeem pause state, transfer eligibility, and account liquidity for any posted collateral that is removed.

### Protocol-Specific Implementations

Curvance supports deleverage through concrete PositionManager implementations:

* `SimplePositionManager`: generic non-native ERC20 paths using a single swap.
* `SingleSidedVaultPositionManager`: ERC4626-style vault positions that use the simple deleverage path.
* `DualSidedVaultPositionManager`: vault positions that redeem vault shares before optionally swapping into the debt asset.
* `NativeVaultPositionManager`: native-token vault positions that inherit the simple deleverage path.
* `PendlePTPositionManager`: Pendle principal-token positions.

Each implementation provides specialized logic for handling the unique characteristics of its respective protocol when exiting positions.

### Access Control and Delegation

PositionManager deleverage operations can be initiated by:

* the position owner through `deleverage(action, slippage)`;
* a delegate through `deleverageFor(action, account, slippage)`, if the account approved that delegate on the specific PositionManager contract with `setDelegateApproval(delegate, true)`.

### Slippage Protection

Deleverage has several slippage-related controls:

* the top-level `slippage` parameter on `deleverage` and `deleverageFor`, checked by `BasePositionManager.checkSlippage` against the account's collateral-minus-debt value before and after the action;
* each `SwapperLib.Swap.slippage`, checked by `_swapSafe` using oracle-priced input and output value;
* external swap calldata minimum-output checks, enforced through the approved calldata checker for the swap target;
* manager-specific floors, such as `repayAssets`, Pendle minimum-output fields, or Velodrome PT exit and route constraints.

The top-level `checkSlippage` modifier is a sanity check, not a complete replacement for route quoting and swap-level limits.

### Example Flow

A typical simple ERC20 deleverage flow:

1. The user has a WETH collateral position and USDC debt in the same isolated market.
2. The user calls `deleverage(action, slippage)`, where `action.cToken` is the WETH cToken and `action.borrowableCToken` is the USDC cToken.
3. The PositionManager calls `withdrawByPositionManager(action.collateralAssets, owner, action)` on the WETH cToken.
4. The WETH cToken withdraws WETH underlying to the PositionManager and calls `onRedeem`.
5. The concrete PositionManager route swaps WETH to USDC according to `swapActions`.
6. The base callback checks that the PositionManager holds at least `action.repayAssets` USDC, reads fresh debt, and calls `repayFor(...)` on the USDC cToken.
7. Remaining swept balances are returned to the owner.
8. Swap-target and debt-cToken allowances opened by the flow are removed when the relevant helper reaches its cleanup path.

The flow reduces collateral and debt in one transaction while still relying on cToken and MarketManager checks for repayment, redemption, liquidity, pause state, cooldowns, and route slippage.

### User Interaction Functions

#### deleverage()

**Description:** Deleverages an active Curvance position for `msg.sender` by redeeming collateral-side assets, converting them through the concrete PositionManager route, repaying debt, and checking pre/post account value through `checkSlippage`.

**Contract:** `BasePositionManager`, inherited by the concrete PositionManager used for the market.

**Function signature:**

```solidity
function deleverage(
        DeleverageAction calldata action,
        uint256 slippage
) external;
```

<table><thead><tr><th width="185">Type</th><th width="171">Name</th><th>Description</th></tr></thead><tbody><tr><td>DeleverageAction</td><td>action</td><td>Struct containing the collateral cToken, collateral underlying amount, debt cToken, minimum debt-asset output floor, swap route, and manager-specific auxiliary data.</td></tr><tr><td>uint256</td><td>slippage</td><td>Top-level PositionManager slippage tolerance in WAD, checked against account value before and after the action.</td></tr></tbody></table>

**Events:**

```solidity
// In the collateral cToken path, inherited from ERC20, emitted when shares burn.
event Transfer(address indexed from, address indexed to, uint256 amount);

// In BaseCToken.sol, emitted only if posted collateral is reduced.
event CollateralUpdated(uint256 shares, bool increased, address account);

// In BorrowableCToken.sol.
event Repay(uint256 assets, uint256 debtAssetsOwed, address payer, address account);
```

***

#### deleverageFor()

**Description:** Deleverages an active Curvance position for `account` through PositionManager delegation. The account must approve the caller on the specific PositionManager contract with `setDelegateApproval(delegate, true)`.

**Contract:** `BasePositionManager`, inherited by the concrete PositionManager used for the market.

**Function signature:**

```solidity
function deleverageFor(
        DeleverageAction calldata action,
        address account,
        uint256 slippage
) external;
```

<table><thead><tr><th width="172">Type</th><th width="159">Name</th><th>Description</th></tr></thead><tbody><tr><td>DeleverageAction</td><td>action</td><td>Structure containing deleverage operation details including position token, collateral amount, borrow token, swap data, repay amount, and auxiliary data.</td></tr><tr><td>address</td><td>account</td><td>The account to deleverage an active Curvance position for.</td></tr><tr><td>uint256</td><td>slippage</td><td>Slippage accepted by the user for the deleverage action, in WAD (1e18).</td></tr></tbody></table>

**Events:**

```solidity
// In the collateral cToken path, inherited from ERC20, emitted when shares burn.
event Transfer(address indexed from, address indexed to, uint256 amount);

// In BaseCToken.sol, emitted only if posted collateral is reduced.
event CollateralUpdated(uint256 shares, bool increased, address account);

// In BorrowableCToken.sol.
event Repay(uint256 assets, uint256 debtAssetsOwed, address payer, address account);
```


# Dynamic Liquidation Engine (DLE)

### Overview

The Dynamic Liquidation Engine (DLE) is Curvance's liquidation framework for isolated markets. It combines position-health-based liquidation math in the Market Manager, public liquidation entrypoints on borrowable cTokens, bad-debt recognition in the debt cToken, and an optional Atlas-based auction path for OEV capture.

### Technical Architecture

#### MEV Integration with Atlas

The contract integrates MEV (Maximal Extractable Value) capture:

* **ME Auction Mechanism:** Auction-based liquidations can run after an oracle update or after a non-oracle state change, such as interest accrual, makes liquidation available. Off-chain auction and solver ordering are handled outside the core lending contracts.
* **Atomic Execution:** In the oracle-update path, the auction flow can update the oracle price and execute the liquidation in the same transaction. The auction manager also supports an auction path that intentionally skips oracle updates.
* **Bid Distribution:** Auction revenue is accumulated by the auction manager and distributed according to its configured revenue split, including the Curvance Protocol destination.
* **Collateral Targeting:** Each auction targets a specific cToken, both the market and the target cToken must be unlocked via the transient storage during the transaction, otherwise liquidation reverts.

#### Auction Buffer System

The contract implements a liquidation buffer that gives auction transactions priority access to liquidations:

* **Buffer Mechanism:** A buffer that gives auction transactions priority for liquidations by applying this discount when pricing collateral.
  * Correlated: 10 bps `(AUCTION_BUFFER = 9990)`
  * Uncorrelated: 50 bps `(AUCTION_BUFFER = 9950)`
* **Implementation:** The Market Manager applies `AUCTION_BUFFER` only when the market is unlocked for the current auction transaction and the requested collateral token matches the transiently unlocked cToken. If only one side is unlocked, the attempted auction liquidation reverts.
* **Benefits:**
  * Captures liquidation opportunities from oracle updates and from non-oracle state changes, such as debt growth through interest accrual.
  * Compensates for execution latency.

#### Transient Storage Risk Parameters

Auction-specific liquidation overrides are managed through transient storage:

* **Liquidation Incentive:** An auction flow can supply a liquidation incentive within the selected collateral token's configured min/max bounds. Passing zero tells the Market Manager to derive the incentive from `lFactor`.
* **Close Factor:** An auction flow can supply a close factor within the selected collateral token's configured min/max bounds. Passing zero tells the Market Manager to derive the close factor from `lFactor`.
* **Transient Nature:** The selected collateral token, incentive, and close factor exist only for the transaction.
* **Validation:** The Market Manager validates the caller, token listing, collateral eligibility, and configured min/max bounds before storing the transient values.

#### Liquidation Types

The contract supports token-specific liquidations:

* **Token-Specific Liquidations:**
  * Target a selected collateral cToken and debt cToken pair.
  * In an auction flow, `unlockAuctionForMarket()` and `setTransientLiquidationConfig()` identify the market and collateral cToken that can receive the auction buffer and any auction-specific close-factor or incentive override.
  * The same liquidation path can produce no liquidation, soft liquidation, hard liquidation, or bad-debt realization depending on the account's `lFactor`, selected debt amount, and available posted collateral.

#### Multi-Liquidation Support

The system is designed to efficiently process liquidations:

* **Batch Processing:** Can handle multiple liquidations in a single transaction.
* **Cached Pricing:** Retrieves asset prices once per transaction rather than per liquidation.
* **Gas Optimization:** Significantly reduces gas costs during liquidation cascades.
* **Debt Repayment Rollup:** Combines debt repayment and bad debt recognition into a single action (1,000 liquidations requires 1 debt token transfer, not 1,000 transfers).

### lFactor (Liquidation Factor)

**Definition:** A continuous score showing how close a position is to hard liquidation.

{% hint style="info" %}
lFactor is denominated in WAD aka 1e18.
{% endhint %}

**Calculation:**&#x20;

1. **Soft Requirement Debt Limit:** for each collateral asset, take its USD value and divide by that asset’s soft collateral requirement; add across assets. If an auction liquidation is active, apply the auction buffer discount to this total.
2. **Hard Requirement Debt Limit:** same as above but using each asset’s hard requirement; apply the same auction buffer if applicable.

* If Debt ≤ **Soft Requirement Debt Limit**: lFactor = 0
* If Debt ≥ **Hard Requirement Debt Limit**: lFactor = 1e18
* Otherwise, lFactor = (debt - soft requirement debt limit) / (hard requirement debt limit- soft requirement debt limit), using rounding up.

**Why it matters:**

* When no auction override is supplied, close factor and liquidation incentive scale linearly with `lFactor`. The Market Manager computes `closeFactor = closeFactorBase + closeFactorCurve * lFactor / WAD` and `liqInc = liqIncBase + liqIncCurve * lFactor / WAD`. Nonzero auction-supplied values bypass that derived curve for the supplied field.

#### Validation Process

Liquidation attempts undergo multiple validation checks.

For example, we check if collateral is unlocked for auction transactions:

```solidity
// Will revert if this liquidation is an attempted auction liquidator
// and liquidator has chosen incorrect collateral or market.
// Pulls any relevant offchain liquidation configuration.
(tData.auctionBuffer, aData.liqInc, aData.closeFactor) =
    _checkLiquidationConfig(collateralToken);
```

### Dual-Oracle Architecture

For each supported asset, Curvance can simultaneously integrate with two independent oracle providers. This creates redundancy and provides additional verification of asset prices, with the following benefits:

* **Enhanced security:** Mitigates risk from oracle failures or manipulation.
* **Continuous operation:** Ensures liquidations can proceed even in volatile markets.
* **Safety Buffer:** Creates a safety buffer when valuing collateral during distressed situations.

#### Price Selection Logic

{% hint style="warning" %}
**The dual-oracle architecture exists in the codebase, but it is not used in the current live implementation.** Live deployments use a single configured oracle source per asset, with heartbeat checks and price guards where applicable, especially for pegged or reference-priced assets.
{% endhint %}

When determining an asset's price, Curvance employs the "most safe" selection algorithm:

1. Each oracle reports its price for the asset.
2. The system applies sanity checks to both reported prices:
   1. Deviation from previous price must not exceed configured limits.
   2. Price must be above minimum threshold (non-zero).
   3. Oracle must have reported within the maximum allowable reporting window.
3. **For borrowable assets, the system selects the higher of the two valid prices.**
4. **For collateral assets, the system selects the lower of the two valid prices.**

```solidity
// inside of _getLiquidationConfig()

(tData.collateralSharesPrice, tData.debtUnderlyingPrice) =
    CommonLib._oracleManager(centralRegistry)
    .getPriceIsolatedPair(collateralToken, debtToken, 2);
```

This approach ensures that in liquidation scenarios, the protocol always errors on the side of protecting itself from bad debt, while giving borrowers the benefit of the most favorable valid price.

### Oracle Pricing During Liquidation

Curvance prices liquidation pairs through the Oracle Manager. The liquidation path calls `getPriceIsolatedPair(collateralToken, debtToken, BAD_SOURCE)`, which prices the collateral cToken shares with the lower-side price and the debt token underlying with the higher-side price.

If the oracle path reports a `BAD_SOURCE` level error, the liquidation reverts. Live deployments rely on configured single-source oracle feeds with heartbeat and price-guard defenses.

#### Risk Mitigation

The system incorporates several safeguards:

* **Transient Storage:** Ensures parameters reset after each transaction.
* **Permission Checks:** Restricts access to orderflow auction functions to authorized addresses.
* **Collateral Locking:** Prevents liquidation of unauthorized collateral during Atlas transactions.

### Protocol Benefits

* **Value Capture:** Extracts MEV from liquidations that would otherwise go to third parties.
* **Liquidation Efficiency:** Ensures timely liquidations even during high volatility.
* **Gas Optimization:** Uses efficient code combined with transient storage for parameter management.
* **Graceful Degradation:** Protocol remains secure even if OEV auctions fail.

By implementing this sophisticated liquidation system, Curvance balances the needs of protocol security, liquidator incentives, and value capture, creating a powerful framework for position management across diverse market conditions.

***


# Orderflow Auction System

## Overview

The Curvance orderflow auction system combines off-chain liquidation auctions with on-chain enforcement to enable:

* Dynamic liquidation penalty and close factor per auction.
* Priority access via a per‑market auction buffer (10 bps for correlated markets, 50 bps for uncorrelated).
* Efficient batch liquidations with cached pricing.
* Strict permissions and correctness guards via transient storage.

On-chain logic refers to this as 'auction-based liquidations' and integrates cleanly with any off-chain solver/auction stack (e.g. Atlas/Fastlane).

## System Architecture

The Curvance orderflow auction system integrates on-chain and off-chain components to enable efficient liquidations with dynamic liquidation sizes and penalty rates.

1. **Off-Chain**
   1. Price monitoring services detect changes.
   2. Auction framework coordinates liquidator bidding.
   3. Bundler constructs a single transaction for the winning sequence of liquidations.
2. **On-Chain**
   1. CentralRegistry unlocks the specific market for the current transaction.
   2. MarketManager handles liquidation execution and validates configuration.
   3. Dynamic Penalty System manages incentive and close factor via transient storage.
   4. Auction Buffer System provides priority access to liquidations.

These components work together to process liquidations with optimal penalty rates while maximizing value capture and minimizing bad debt risk.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FwDscsrSxej5yemiPiz5P%2Fimage.png?alt=media&amp;token=5d66c192-272b-457f-930d-a359ace2e55a" alt=""><figcaption></figcaption></figure>

#### Key Features

* **Dynamic Liquidation Penalties:** Adjustable within per-token min/max ranges, set per-transaction via transient storage.
* **Transient Storage:** Parameters apply only within the current transaction then reset.
* **Oracle Integration:** Dual-oracle pathing with an isolated pair fetch price during liquidation to prioritize risk mitigation.
* **Liquidation Buffer:** Per‑market advantage, 10 bps (correlated) or 50 bps (uncorrelated).
* **Efficient Batch Liquidations:** Single config/pricing fetch used across multiple liquidations in one transaction.

#### Auction workflow

1. **Oracle Update & Operation Collection:** Price feed updates create liquidation opportunities. Liquidators generate signed UserOps containing bid amounts and execution instructions.
2. **Auction & Bundling:** Auctioneer receives bids from liquidators in the form of signed AA (Account Abstraction) operations. The auctioneer then selects highest bidding operations and bundles them into a single atomic transaction with itself as `tx.origin`.
3. **Transaction Execution:** The bundled transaction executes on the Auctioneer Entrypoint contract, which:
   * Performs pre-execution balance checks.
   * Calls each liquidator's operation in descending bid order.
   * Verifies bid payment through post-execution balance checks.
   * Continues to the next highest bidder if a liquidator fails to pay.

This mechanism maximizes MEV capture while ensuring liquidations always complete in a timely manner.

### Core Dataflows

#### Price Update to Liquidation Flow

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FxVp69cppyDyUZ7DgmJj6%2Fimage.png?alt=media&amp;token=9a6a9fe5-e7f1-479a-bc10-5f6526201032" alt=""><figcaption></figcaption></figure>

The journey from price change to liquidation execution follows these steps:

1. Price change detected off-chain.
2. Auction runs and selects winners.
3. Bundler submits a transaction that:
   1. Unlocks the market in CentralRegistry.
   2. Sets auction parameters + collateral unlock in MarketManager.
   3. Executes batch liquidations.

This auction typically completes within milliseconds off-chain before submitting the transaction.

#### Liquidation Buffer System

The Auction buffer system provides priority access to liquidations:

1. **Buffer Mechanism:** 10 bps buffer for correlated markets or 50 bps buffer for uncorrelated markets. The buffer is applied by multiplying `cSoft` and `cHard` by `AUCTION_BUFFER` during auction liquidations.
2. **Implementation:** When Auction transactions are detected via transient storage, liquidation health checks apply the buffer and shifts borderline positions into liquidatable range in a narrow window.
3. **Benefits:**
   1. Captures liquidations from interest accrual.
   2. Handles LST (Liquid Staking Token) yield accumulation.
   3. Compensates for network latency.

```solidity
    /// @notice Buffer to ensure orderflow auction-based liquidations have
    ///         priority versus basic liquidations, for correlated assets,
    ///         in `BPS`.
    /// @dev 9990 = 99.9%. Multiplied then divided by `BPS` = 10 bps buffer
    ///                    auction liquidation priority for correlated assets.
    uint256 public constant AUCTION_BUFFER_CORRELATED = 9990;
    /// @notice Buffer to ensure orderflow auction-based liquidations have
    ///         priority versus basic liquidations, for uncorrelated assets,
    ///         in `BPS`.
    /// @dev 9950 = 99.5%. Multiplied then divided by `BPS` = 50 bps buffer
    ///                    auction liquidation priority for uncorrelated assets.
    uint256 public constant AUCTION_BUFFER_UNCORRELATED = 9950;
```

```solidity
   function _checkLiquidationConfig(
        address cToken
    ) internal view returns (uint256, uint256, uint256) {
        (address unlockedCToken, uint256 liqIncentive, uint256 closeFactor) =
            getTransientLiquidationConfig();

        bool unlockedMarket = centralRegistry.isMarketUnlocked();
        bool unlockedCollateral = unlockedCToken == cToken;

        if (unlockedMarket || unlockedCollateral) {
            // This is an attempted auction liquidation, and is configured
            // correctly so give them the auction priority buffer.
            if (unlockedMarket && unlockedCollateral) {
                return (AUCTION_BUFFER, liqIncentive, closeFactor);
            }

            // This is an attempted auction liquidation, but its misconfigured
            // and unauthorized because of this.
            revert MarketManager__UnauthorizedLiquidation();
        }

        // This is not an attempted auction liquidation, so approve the
        // liquidation, but without any offchain liquidation config values.
        return (0, 0, 0);
    }
```

#### Dynamic Penalty Dataflow

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FKiSbNrcNGDOAe0B5Uamk%2Fimage.png?alt=media&amp;token=4e85a5dd-6a48-467c-952b-ca92d5730b6c" alt=""><figcaption></figcaption></figure>

The dynamic penalty mechanism operates as follows:

1. Liquidators submit bids indicating desired liquidation incentive (stored as BPS + incentive) and close factor (BPS). Passing 0 for either uses protocol‑derived values. Both are validated against per‑token min/max bounds.
2. During the auction transaction, the authorized caller:
   1. Unlocks the market in CentralRegistry.
   2. Calls `setTransientLiquidationConfig(cToken, incentive, closeFactor)` on MarketManager, which:
      1. Validates bounds against token min/max.
      2. Packs collateral address, incentive, and close factor into one transient slot.
3. Liquidation executes using these transient values.
4. After the transaction, parameters reset automatically; they can also be explicitly reset mid-bundle if needed.

Per-transaction auction parameters are packed into a single transient word:

* \[0..159] unlocked collateral cToken address.
* \[160..175] liquidation incentive (BPS, stored as BPS + incentive).
* \[176..191] close factor (BPS).

```solidity
uint256 internal constant _TRANSIENT_LIQUIDATION_CONFIG_KEY
    = 0x1966ec4daf81281b2aba49348128e9b155301b8486bde131e0db16a52b730b82;
uint256 internal constant _BITMASK_COLLATERAL_UNLOCKED = (1 << 160) - 1;
uint256 internal constant _BITPOS_LIQ_INCENTIVE = 160;
uint256 internal constant _BITPOS_CLOSE_FACTOR = 176;
```

```solidity
    function setTransientLiquidationConfig(
        address cToken,
        uint256 incentive,
        uint256 closeFactor
    ) external {
        _checkAuctionPermissions();
        _checkIsListedToken(cToken);

         CurvanceToken memory c = _tokenConfig[cToken];

        // Make sure this token actually can be liquidated, by being
        // collateralizable in the first place.
        if (c.collRatio == 0) {
            revert MarketManager__UnauthorizedLiquidation();
        }

        // Validate `incentive` is within configured incentive bounds, unless
        // zero is passed to signal protocol-derived values should be used.
        // This also validates `incentive` is not > the 16 bits we have allocated.
        if (
            incentive != 0 &&
            (incentive < c.liqIncMin || incentive > c.liqIncMax)
        ) {
            _revert(_INVALID_PARAMETER_SELECTOR);
        }

        // Validate `closeFactor` is within configured allowed close factor
        // range, unless zero is passed to signal protocol-derived values
        // should be used. This also validates `closeFactor` is not > the 16
        // bits we have allocated.
        if (
            closeFactor != 0 &&
            (closeFactor < c.closeFactorMin || closeFactor > c.closeFactorMax)
        ) {
            _revert(_INVALID_PARAMETER_SELECTOR);
        }

        uint256 liqConfig = uint256(uint160(cToken));
        assembly {
            // Mask `liqConfig` to the lower 160 bits, in case the upper bits
            // somehow are not clean.
            liqConfig := and(liqConfig, _BITMASK_COLLATERAL_UNLOCKED)
            // Equals: liqConfig | (incentive << _BITPOS_LIQ_INCENTIVE) |
            //         closeFactor << _BITPOS_CLOSE_FACTOR.
            liqConfig := or(
                liqConfig,
                or(
                    shl(_BITPOS_LIQ_INCENTIVE, incentive),
                    shl(_BITPOS_CLOSE_FACTOR, closeFactor)
                )  
            )

            tstore(_TRANSIENT_LIQUIDATION_CONFIG_KEY, liqConfig)
        }
    }
```

```solidity
    function resetTransientLiquidationConfig() external {
        _checkAuctionPermissions();

        /// @solidity memory-safe-assembly
        assembly {
            tstore(_TRANSIENT_LIQUIDATION_CONFIG_KEY, 0)
        }
    }
```

```solidity
    function getTransientLiquidationConfig() public view returns (
        address cTokenUnlocked,
        uint256 incentive,
        uint256 closeFactor
    ) {
        uint256 liqConfig;
        /// @solidity memory-safe-assembly
        assembly {
            liqConfig := tload(_TRANSIENT_LIQUIDATION_CONFIG_KEY)
        }

        cTokenUnlocked = address(uint160(liqConfig));
        incentive = uint16(liqConfig >> _BITPOS_LIQ_INCENTIVE);
        closeFactor = uint16(liqConfig >> _BITPOS_CLOSE_FACTOR);
    }
```

{% hint style="info" %}
Important unit notes:

* Liquidation incentive is represented as BPS + incentive (e.g. 10500 -> 5% incentive). Transient incentive must use this representation to pass range checks.
* Close factor is in BPS (0 <-> 10000).
  {% endhint %}

#### Fallback Mechanism

When auction parameters are not set (transient values are zero), the system uses the base liquidation incentive an interpolated close factor configured by governance, computed from the account's lFactor.

{% hint style="info" %}
**Note:** No auction buffer is applied in non-auction liquidations.
{% endhint %}

```solidity
// In _getLiquidationConfig() inside of MarketManagerIsolated.sol
// fallback to base curve when transient is zero.

        // We only need to cache these variables if we did not receive close
        // factor/liquidation incentive from `getLiquidationConfig`.
        if (aData.closeFactor == 0 || aData.liqInc == 0) {
            aData.closeFactorBase = c.closeFactorBase;
            aData.closeFactorCurve = c.closeFactorCurve;
            aData.liqIncBase = c.liqIncBase;
            aData.liqIncCurve = c.liqIncCurve;
        }
```

```solidity
// In _canLiquidate() inside of MarketManagerIsolated.sol 
      
        // If this liquidation does not have offchain submitted
        // parameters then closeFactorCurve will not be 0. We know this since
        // closeFactorCurve is BPS - closeFactorBase and closeFactorBase is
        // limited to MAX_BASE_CFACTOR meaning closeFactorCurve cannot ever be
        // 0 unless we did not receive offchain parameters and we need to
        // calculate close factor and liquidation penalty onchain.
        if (aData.closeFactorCurve != 0) {
            aData.closeFactor = aData.closeFactorBase +
                _mulDiv(aData.closeFactorCurve, aData.lFactor, WAD);
            aData.liqInc = aData.liqIncBase +
                _mulDiv(aData.liqIncCurve, aData.lFactor, WAD);
        }
```

#### Market and Collateral Unlocking Mechanism

1. Market unlock: performed via CentralRegistry (transient, per-tx).
2. Collateral unlock + parameters: set in MarketManager (transient, per-tx).
3. Both market unlock (`CentralRegistry`) and cToken unlock (`setTransientLiquidationConfig`) must be set in‑tx; otherwise, auction attempts revert `MarketManager__UnauthorizedLiquidation`.

```solidity
    function unlockAuctionForMarket(address marketToUnlock) external {
        if (!hasAuctionPermissions[msg.sender]) {
            revert CentralRegistry__Unauthorized();
        }

        // Validate that you're unlocking an approved market manager.
        if (!isMarketManager[marketToUnlock]) {
            revert CentralRegistry__InvalidParameter();
        }

        uint256 marketToUnlockUint = uint256(uint160(marketToUnlock));
        /// @solidity memory-safe-assembly
        assembly {
            tstore(_TRANSIENT_MARKET_UNLOCKED_KEY, marketToUnlockUint)
        }
    }
```

```solidity
    function isMarketUnlocked() public view returns (bool isUnlocked) {
        uint256 result;
        /// @solidity memory-safe-assembly
        assembly {
            result := tload(_TRANSIENT_MARKET_UNLOCKED_KEY)
        }

        // CASE: This is not an Auction tx, so allow all markets,
        // and return false, the caller is not approved for auction-based
        // liquidations. 
        if (result == 0) {
            return isUnlocked;
        }

        // True if the caller is approved for auction-based liquidations,
        // otherwise false.
        isUnlocked = uint256(uint160(msg.sender)) == result;
    }
```

### Batch Liquidation Support

The system supports efficient multi-liquidation processing:

1. **Bulk Processing:** Can handle multiple liquidations in a single transaction.
2. **Cached Pricing:** Retrieves asset prices once per transaction rather than per liquidation.
3. **Consolidated Transfers:** Aggregates results and returns both seized shares and debt to repay into a single transfer.
4. **Gas Optimization:** Significantly reduces gas costs during liquidation cascades.

This design allows for processing thousands of liquidations in a single transaction, improving efficiency during market stress.

### Auction Mechanism

#### Auction structure

* A brief solver auction is coordinated whenever a liquidation opportunity is detected. Auction-based liquidations are the primary path and have slight priority (\~10 bps) versus traditional; they can also backrun oracle updates via Redstone.
* The user operation can:
  * Call `update(oracle, calldata)` to push fresh prices via an allowed oracle route, or
  * Call `initiateAuction()` to begin an auction without an oracle update (e.g., interest-accrual-triggered events).

An off-chain auction is started with the Atlas SDK; a winner is determined in \~300 ms. The Fastlane bundler executes via account abstraction to the AEE, including the winning bid plus up to 9 fallback bids (max 10).&#x20;

Auctions are scoped per `(collateralToken, marketManager);` combined auctions are split/ordered accordingly. During execution, the AEE calls the Curvance Auction Manager to unlock the market/collateral and set transient liquidation parameters (penalty, close factor) within each market's allowed bounds.

Liquidation proceeds only if the solver transfers the native-token bid to the AEE, which forwards it to the Auction Manager as MEV revenue. Multiple auctions can be executed sequentially in a single meta call; transient storage resets at the end of the transaction, relocking auctions until a new auction is initiated.

Solvers bid in the native gas token; Atlas ranks bids by bid amount and attempts the highest first.

#### On-chain control responsibilities

* Validate user operations (sender, target, and allowed entry functions).
* Enforce allowed oracle routes for price updates (per-oracle, per-selector allowlist).
* For each solver attempt, unlock the market for auction liquidations and activate transient liquidation parameters within market-defined bounds.
* Scope auctions per `(collateralToken, marketManager)` and reset transient storage at the end of the transaction.
* On the winning solver, collect the native-token bid and account it as MEV revenue.

#### Bid Model

**Bid components**

* **Solver address:** The solver contract that will execute liquidation.
* **Execution data:** Includes liquidation call parameters; the tail of the calldata provides `(collateralToken, marketManager)` for pre-solver setup.
* **Bid amount (MEV):** A scalar amount in the native gas token used exclusively for ranking.
* **Signature(s):** Signatures proving authenticity (user op and solver op).

{% hint style="info" %}
Liquidation penalty and close factor are not used for off-chain bid ranking in this integration. They are enforced on-chain by market configuration during the pre-solver step and must lie within each market’s allowed bounds.
{% endhint %}

**Bid ranking**

* **Primary:** Higher native-token bid wins.
* **Ties:** Resolved by arrival order/timestamp.

### Data Storage Model

The system employs two distinct storage layers:

**Permanent Storage:**

* Penalty ranges (min, max, default values).
* Account health statuses.
* System configuration parameters.

**Transient Storage:**

* Dynamic penalty values during auction actions.
* Close factor values.
* Collateral unlock status.
* Values automatically reset after transaction completion.

This separation ensures critical data persists while dynamic values remain ephemeral.

***

Resources:

Atlas Docs: <https://atlas-docs.pages.dev/atlas/introduction>


# Bad Debt Socialization

### Overview

Bad Debt Socialization is a critical risk management mechanism in the Curvance Protocol that handles scenarios where a borrower's collateral value falls below their outstanding debt. Rather than leaving the protocol with uncovered losses, this mechanism distributes the shortfall equitably among all lenders in the affected market, preserving system solvency while minimizing individual impact.

### Key Components

#### System Actors

* **Liquidators:** External agents who repay a portion of defaulted debt in exchange for collateral.
* **Borrowers:** Users with debt positions that may become undercollateralized.
* **Lenders:** cToken holders who collectively absorb any shortfall from liquidations.
* **Market Manager:** Orchestrates the liquidation and bad debt socialization process.

### Process Flow

#### 1. Bad Debt Detection

The system identifies positions where collateral value is insufficient to cover debt:

```
Collateral Value < (Debt * Liquidation Incentive) = Bad Debt Condition
```

#### 2. Asset-Specific Liquidation

When bad debt is detected:

* Each collateral asset is evaluated individually for liquidation.
* Liquidations occur on a per-asset basis rather than requiring full account liquidation.
* Multiple liquidations can be processed efficiently in a single transaction.

#### 3. Socialization Execution

When a liquidator executes the bad debt liquidation:

1. The system calculates total debt to be closed for the specific asset.
2. Determines how much can be repaid via the liquidator's token transfer.
3. Calculates the remainder as bad debt to be socialized.
4. The shortfall is distributed proportionally across all lenders of that asset.

```
Bad Debt = (Total Account Debt * Liquidation Incentive) - Collateral Value
Or, more simply:
Bad Debt = Total Account Debt - Liquidator Repayment
```

For example, if a borrower has $900 in debt with $850 in collateral during a liquidation with 5% liquidation incentive, lenders collectively absorb $95 (900 \* 1.05 - 850 = 945 - 850 = 95) of the debt as a loss. If the borrower is liquidated across two liquidations, 25% initially then the remaining 75%, borrowers would recognize $23.75 and then $71.25 as bad debt across the liquidations.

#### 4. State Transitions

The process follows these state transitions:

* Normal Operation → Bad Debt Detection: When collateral falls below debt.
* Bad Debt Detection → Asset Liquidation: Individual asset liquidation is triggered.
* Asset Liquidation → Socialization: Execution of liquidation with partial repayment.
* Socialization → Normal Operation: System returns to normal state with debt cleared.

### Technical Implementation

The socialization mechanism operates through a coordinated interaction between:

1. **Debt Calculation:** For each liquidation, the system:
   1. Calculates the total debt to be closed.
   2. Determines how much can be repaid through liquidation transfers.
   3. Subtracts this from total debt to find the bad debt amount.
2. **Efficient Processing:**
   1. Multiple liquidations can be batched in a single transaction.
   2. Gas optimization by consolidating repayments and bad debt calculations.
3. **cToken Integration:**
   1. The cToken contract recognizes unpaid debt by adjusting `totalBorrows` .
   2. This maintains the exchange rate mechanism while distributing losses.
4. **Atlas Integration:**
   1. Auctions prioritize liquidations to minimize bad debt through efficient market mechanisms.
   2. Buffer system ensures auctions get priority for executing liquidations.
   3. Dynamic penalty system incentivizes liquidators appropriately based on market conditions.

### Security Considerations

* **Gas Efficiency:** Optimized implementation handles thousands of liquidations efficiently, even during market stress.
* **Edge Cases:** System handles rounding issues with conservative approaches that favor protocol solvency.
* **Transparent Tracking:** Bad debt events are fully transparent with on-chain events.

This streamlined mechanism ensures the protocol remains solvent even in extreme market conditions while distributing any unavoidable losses fairly among participants, with minimal gas costs and maximum transparency.


# Oracles

### Overview

Curvance routes protocol price reads through `OracleManager`. Markets and cTokens use this manager to price supported assets, Curvance cToken shares, collateral, and debt through a single integration surface.

The oracle layer has three jobs:

* Map each supported asset to its configured pricing adaptor.
* Normalize adaptor responses into 18-decimal price values.
* Return an error code that tells calling contracts whether the price can be used for the requested operation.

{% hint style="info" %}
Live market configuration is single-source per asset. The codebase contains dual-adaptor support, but external integrations should treat the active path as one approved adaptor per asset with heartbeat checks, positive-price checks, and optional price guards for pegged or reference-priced assets.
{% endhint %}

### Architecture

#### `OracleManager`

`OracleManager` is the protocol-facing price hub. It stores approved adaptors, per-asset pricing configuration, and cToken-to-underlying mappings.

The manager exposes:

* `getPrice(asset, inUSD, getLower)` for direct price reads.
* `getPriceIsolatedPair(collateralToken, debtToken, errorCodeBreakpoint)` for isolated-market collateral and debt pricing.
* `getPricesForMarket(account, assets, errorCodeBreakpoint)` for account-level market checks.
* `getPricingAdaptors(asset)` and `isSupportedAsset(asset)` for integration discovery.

When an input asset is a registered Curvance cToken, `getPrice()` resolves its underlying asset first, reads the underlying price, then multiplies by the stored cToken exchange rate. Lower-bound reads round down, and upper-bound reads round up.

#### Adaptors

Adaptors implement `IOracleAdaptor`. They are responsible for source-specific validation and return an `IOracleAdaptor.PricingResult` with:

* `price`: the reported price.
* `inUSD`: whether the returned price is denominated in USD or the chain's native token.
* `hadError`: whether the adaptor found an invalid, stale, unsupported, or guarded price.

`BaseOracleAdaptor` provides the shared adaptor model: supported-asset tracking, per-asset price guards, price normalization to WAD scale, heartbeat-style data validation, and permission checks for configuration.

The current oracle source map includes three high-level categories:

* Direct push-feed adaptors for externally reported prices.
* Wrapped aggregator helpers that adjust an underlying feed by an exchange rate.
* Pendle-specific adaptors that compose a quote-asset price with a TWAP-derived rate.

Direct feed and Pendle adaptor paths converge at `IOracleAdaptor.getPrice()` before `OracleManager` evaluates the result. Wrapped aggregator helpers sit behind compatible feed adaptors and adjust the value those adaptors read.

#### Price Guards

Price guards live on adaptors, not on `OracleManager`. A guard can set a minimum price and a maximum price for an asset in a specific denomination. If a guarded price falls below the configured minimum, the adaptor returns a zero price that bubbles up as an oracle error. If the price is above the configured maximum, the adaptor clamps the returned price to the maximum.

Price guards are configured for pegged or reference-priced assets where bounded movement is part of the market design. Volatile assets should not be documented as having guards by default.

See [Price Guard](/app/developer-docs/oracles/price-guard) for the guard configuration model.

### Pricing Flow

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FH0oKyVnQT2BAPMgpVUjD%2FChatGPT%20Image%20May%205%2C%202026%2C%2005_09_37%20PM.png?alt=media&amp;token=35b04aff-4b60-4b8f-8e18-0ea596c38bc1" alt=""><figcaption></figcaption></figure>

1. A market, cToken, or integrator calls `OracleManager`.
2. If the input is a registered cToken, the manager resolves the underlying asset from `cTokens`.
3. The manager loads the asset's configured adaptor from `assetPricingConfig`.
4. The manager calls `IOracleAdaptor.getPrice(asset, inUSD, getLower)`.
5. The adaptor validates source data, normalizes the price, applies any configured guard, and returns `PricingResult`.
6. If the adaptor returned the opposite denomination, the manager converts between USD and the chain's native token by pricing the native token through the same manager.
7. If the original input was a cToken, the manager multiplies the underlying price by the stored cToken exchange rate.
8. The manager returns `(price, errorCode)` or reverts in market-specific helpers when the returned error code reaches the caller's `errorCodeBreakpoint`.

The `getLower` flag controls conservative pricing direction. Collateral-side reads use the lower price, while debt-side reads use the higher price. This keeps solvency checks pessimistic for both sides of a position.

`getPriceIsolatedPair()` prices collateral cToken shares with the lower underlying price and a fresh exchange rate, then prices debt underlying with the higher underlying price. `getPricesForMarket()` follows the same direction per account asset: collateral snapshots use lower share prices, and debt snapshots use higher underlying prices.

### Error Code Model

Oracle reads use numeric error codes:

<table><thead><tr><th width="92">Code</th><th width="132">Source name</th><th>Meaning</th></tr></thead><tbody><tr><td><code>0</code></td><td><code>NO_ERROR</code></td><td>The price read completed without an oracle error.</td></tr><tr><td><code>1</code></td><td><code>CAUTION</code></td><td>A dual-adaptor path found a partial failure or configured deviation. This path exists in source but is not the live single-adaptor model.</td></tr><tr><td><code>2</code></td><td><code>BAD_SOURCE</code></td><td>The price source failed, returned zero, returned stale or invalid data, or could not be trusted for the requested read.</td></tr></tbody></table>

`getPrice()` returns the error code with the price. It can still revert when the asset has no configured pricing path or an adaptor is not approved.

Market-facing helpers enforce the caller's breakpoint:

* `getPriceIsolatedPair(..., errorCodeBreakpoint)` reverts with `OracleManager__ErrorCodeFlagged()` when either side reaches the breakpoint.
* `getPricesForMarket(..., errorCodeBreakpoint)` reverts with `OracleManager__ErrorCodeFlagged()` when any account asset reaches the breakpoint.

Liquidation and solvency paths choose their breakpoint based on the operation. Integrators should not assume every nonzero error code has the same effect across all callers.

### Integration Considerations

* Discover support through `isSupportedAsset(asset)` or `getPricingAdaptors(asset)` before relying on a price read.
* Read real manager and adaptor addresses from the deployment registry for the chain you are integrating. Do not hardcode sample addresses.
* Treat prices as WAD-scaled values unless a downstream contract explicitly documents a different unit.
* Pass `inUSD = true` when matching Curvance market solvency paths, which price collateral and debt in USD.
* Use `getLower = true` for collateral-side previews and `getLower = false` for debt-side previews.
* Do not treat price guards as global protection for every asset. Check adaptor guard configuration when a pegged or reference-priced asset depends on bounded pricing.
* Do not continue a user flow after `BAD_SOURCE`. The manager and market helpers use that condition to block unsafe operations.
* When reading cToken prices, remember that the manager prices cToken shares by combining underlying price with the cToken exchange rate.

### Related Pages

* Oracle Manager
* Price Guard
* Adaptors


# Oracle Manager

### Overview

`OracleManager` is Curvance's protocol-level price hub. Market contracts call it to price underlying assets, cToken shares, collateral positions, and debt positions through a single interface.

The manager does not maintain price feeds itself. It routes each supported asset to one or more approved oracle adaptors, normalizes price results, applies cToken exchange-rate conversion when the queried asset is a Curvance cToken, and returns an error code that downstream contracts use as a safety signal.

Prices are returned with 18-decimal precision. A price can be denominated in USD or in the chain's native gas token, depending on the `inUSD` argument.

### Architecture

`OracleManager` is built around three registries:

| Registry             | Getter                        | Purpose                                                      |
| -------------------- | ----------------------------- | ------------------------------------------------------------ |
| Approved adaptors    | `isApprovedAdaptor(address)`  | Controls which adaptor contracts may be used by the manager. |
| Asset pricing config | `getPricingAdaptors(address)` | Lists the adaptor dependencies for an underlying asset.      |
| Curvance cTokens     | `cTokens(address)`            | Maps a supported cToken to its underlying asset.             |

The constructor stores the protocol `centralRegistry`, and every configuration function checks `centralRegistry.hasElevatedPermissions(msg.sender)` before changing oracle state.

```solidity
constructor(ICentralRegistry cr);
```

#### Supported Asset Model

An asset is supported when it has at least one configured pricing adaptor. A Curvance cToken is supported when it has been registered in `cTokens` and its underlying asset has at least one configured adaptor.

The manager supports up to two adaptor dependencies per asset. Current developer-facing integrations should treat normal pricing as single-source, with adaptor-level freshness and guard checks handled inside the configured adaptor. The two-adaptor code path exists for assets that are configured with two adaptor dependencies, where the manager can compare returned prices against configured deviation bounds.

Adaptors return `IOracleAdaptor.PricingResult`:

| Field      | Meaning                                          |
| ---------- | ------------------------------------------------ |
| `price`    | The adaptor's price value.                       |
| `inUSD`    | Whether `price` is USD-denominated.              |
| `hadError` | Whether the adaptor encountered a pricing issue. |

When an adaptor's denomination does not match the requested denomination, the manager converts through the configured price of the native gas token.

#### cToken Pricing

For registered cTokens, `getPrice()` first prices the underlying asset, then multiplies by the cToken exchange rate:

| Query                                    | Exchange-rate read                           | Rounding |
| ---------------------------------------- | -------------------------------------------- | -------- |
| `getPrice(cToken, ..., true)`            | `exchangeRate()`                             | Down     |
| `getPrice(cToken, ..., false)`           | `exchangeRate()`                             | Up       |
| `getPriceIsolatedPair()` collateral side | `exchangeRateUpdated()`                      | Down     |
| `getPricesForMarket()` collateral side   | `getSnapshotUpdated()` then `exchangeRate()` | Down     |

The market helper functions use updated cToken reads because they support liquidity checks that need current account and exchange-rate data.

### Price Query Flow

1. The caller requests a price through `getPrice(asset, inUSD, getLower)` or through a market helper.
2. The manager checks the optional `SEQUENCER_ORACLE()` uptime feed from the central registry. If no feed is configured, the check returns true.
3. If `asset` is a registered cToken, the manager replaces it with its underlying asset for adaptor pricing.
4. The manager loads the asset's adaptor list and reverts if no adaptor is configured.
5. With one adaptor, it calls `IOracleAdaptor.getPrice(asset, inUSD, getLower)`.
6. With two adaptors, it calls both adaptors, handles adaptor errors, checks deviation bounds, and returns either the lower or higher price based on `getLower`.
7. If the original query was for a cToken, the manager converts the underlying price into a cToken share price.
8. If the resulting price is zero, the manager escalates the result to `BAD_SOURCE`.

Use `getLower = true` when valuing collateral conservatively. Use `getLower = false` when valuing debt conservatively.

### Error Code Handling

`OracleManager` uses numeric error codes from `ConstantsLib`:

<table><thead><tr><th width="138">Code</th><th width="165">Name</th><th>Meaning</th></tr></thead><tbody><tr><td><code>0</code></td><td><code>NO_ERROR</code></td><td>The price was returned without an oracle-manager error signal.</td></tr><tr><td><code>1</code></td><td><code>CAUTION</code></td><td>One source failed in a two-adaptor configuration, or the two adaptor prices exceeded the configured caution bound.</td></tr><tr><td><code>2</code></td><td><code>BAD_SOURCE</code></td><td>No usable price was available, the returned price was zero, the optional uptime-feed check failed, or the two adaptor prices exceeded the configured bad-source bound.</td></tr></tbody></table>

`getPrice()` returns the error code to the caller. Market helper functions take an `errorCodeBreakpoint` and revert with `OracleManager__ErrorCodeFlagged()` when the observed code is greater than or equal to that breakpoint.

This makes the caller's tolerance explicit. For example, a caller that passes `BAD_SOURCE` accepts `CAUTION` but rejects `BAD_SOURCE`; a caller that passes `CAUTION` rejects both `CAUTION` and `BAD_SOURCE`.

### Market Query Helpers

Market-facing helpers price cTokens and account positions in the form used by liquidity checks.

| Function                 | Collateral pricing                                                                                                           | Debt pricing                                                                 | State behavior                                     |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------- |
| `getPriceIsolatedPair()` | Prices the collateral cToken's underlying with `getLower = true`, then converts to share value with `exchangeRateUpdated()`. | Prices the debt cToken's underlying with `getLower = false`.                 | Updates the collateral cToken exchange rate.       |
| `getPricesForMarket()`   | For snapshots marked `isCollateral`, prices the underlying with `getLower = true`, then converts to share value.             | For non-collateral snapshots, prices the underlying with `getLower = false`. | Calls `getSnapshotUpdated(account)` on each asset. |

Both helpers require each provided cToken to be registered in `cTokens`. If a cToken is not registered, the helper reverts with `OracleManager__NotSupported()`.

### Permissions and Configuration

Only addresses with elevated permissions in the central registry can configure the manager. These functions add or remove approved adaptors, attach approved adaptors to assets, configure two-adaptor deviation bounds, and register cTokens.

{% hint style="info" %}
Dual adaptor infrastructure exists on live deployments, but are unused in practice.
{% endhint %}

Deviation bounds are configured in basis points. The manager enforces:

| Constant                          | Value | Meaning                                                            |
| --------------------------------- | ----: | ------------------------------------------------------------------ |
| `MIN_DEVIATION_BOUND`             |  `20` | Minimum allowed caution bound, equal to 0.20%.                     |
| `MAX_DEVIATION_BOUND`             | `350` | Maximum allowed bad-source bound, equal to 3.50%.                  |
| `MIN_CAUTION_TO_BAD_SOURCE_DELTA` |  `20` | Minimum gap between caution and bad-source bounds, equal to 0.20%. |

When bounds are stored, the manager adds `BPS` internally. For example, an input of `200` is stored as `10200` so `_checkBounds()` can compare price ratios directly.

Configuration emits:

| Event                                                                                                                                                  | Emitted when                                    |
| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------- |
| `AdaptorDependencyAdded(address asset, address adaptor)`                                                                                               | An adaptor dependency is added for an asset.    |
| `AdaptorDependencyRemoved(address asset, address adaptor)`                                                                                             | An adaptor dependency is removed from an asset. |
| `AssetDeviationBoundsSet(address asset, uint256 badSourceBoundUSD, uint256 cautionBoundUSD, uint256 badSourceBoundNative, uint256 cautionBoundNative)` | Two-adaptor deviation bounds are set.           |

### Integration Considerations

* Read real contract addresses from the deployment registry for the chain you are integrating with. Use placeholder addresses in examples and application config templates.
* Treat returned prices as 18-decimal values.
* Check both `price` and `errorCode` when calling `getPrice()` directly. A non-reverting call can still return `CAUTION` or `BAD_SOURCE`.
* Use the same conservative direction as protocol liquidity checks: lower prices for collateral, higher prices for debt.
* Use market helpers when reproducing market health checks, because they update snapshots and exchange rates where the protocol expects updated values.
* Do not assume a cToken can be priced directly until `cTokens(cToken)` returns a nonzero underlying and the underlying is supported.
* Do not hardcode adaptor behavior in integrations. The manager exposes adaptor addresses, but source-specific checks are implemented inside each adaptor.

### User Interaction Functions

### Price Reads

#### **getPrice()**

**Description:** Returns the price of an asset or registered cToken and an oracle-manager error code. This is a view function and does not update cToken exchange rates.

**Function signature:**

```solidity
function getPrice(
    address asset,
    bool inUSD,
    bool getLower
) public view returns (uint256 price, uint256 errorCode);
```

**Inputs:**

<table><thead><tr><th width="119">Type</th><th width="120">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>address</code></td><td><code>asset</code></td><td>Underlying asset or registered cToken to price.</td></tr><tr><td><code>bool</code></td><td><code>inUSD</code></td><td><code>true</code> for USD-denominated price, <code>false</code> for native-token-denominated price.</td></tr><tr><td><code>bool</code></td><td><code>getLower</code></td><td><code>true</code> to return the lower value in a two-adaptor setup, <code>false</code> to return the higher value.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="126">Type</th><th width="127">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>uint256</code></td><td><code>price</code></td><td>18-decimal price.</td></tr><tr><td><code>uint256</code></td><td><code>errorCode</code></td><td><code>0</code>, <code>1</code>, or <code>2</code>.</td></tr></tbody></table>

{% hint style="info" %}

* Reverts with `OracleManager__NotSupported()` if the asset has no configured adaptor.
* Returns `(0, BAD_SOURCE)` if the optional `SEQUENCER_ORACLE()` uptime-feed check fails.
* Converts registered cToken prices using `exchangeRate()`.
  {% endhint %}

***

#### **getPriceIsolatedPair()**

**Description:** Returns conservative prices for a collateral cToken and debt cToken pair. This function is used by market logic that needs collateral value in share terms and debt value in underlying terms.

**Function signature:**

```solidity
function getPriceIsolatedPair(
    address collateralToken,
    address debtToken,
    uint256 errorCodeBreakpoint
) external returns (
    uint256 collateralSharesPrice,
    uint256 debtUnderlyingPrice
);
```

**Inputs:**

<table><thead><tr><th width="118">Type</th><th width="212">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>address</code></td><td><code>collateralToken</code></td><td>Registered cToken used as collateral.</td></tr><tr><td><code>address</code></td><td><code>debtToken</code></td><td>Registered cToken used for debt.</td></tr><tr><td><code>uint256</code></td><td><code>errorCodeBreakpoint</code></td><td>Error code threshold that causes a revert.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="109">Type</th><th width="225">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>uint256</code></td><td><code>collateralSharesPrice</code></td><td>Collateral cToken share price after applying <code>exchangeRateUpdated()</code>.</td></tr><tr><td><code>uint256</code></td><td><code>debtUnderlyingPrice</code></td><td>Debt token underlying price.</td></tr></tbody></table>

{% hint style="info" %}

* Reverts with `OracleManager__ErrorCodeFlagged()` when an observed error code is greater than or equal to `errorCodeBreakpoint`.
* Reverts with `OracleManager__NotSupported()` if either cToken is not registered.
  {% endhint %}

***

#### **getPricesForMarket()**

**Description:** Returns updated account snapshots and conservative prices for a list of cTokens in a market.

**Function signature:**

```solidity
function getPricesForMarket(
    address account,
    address[] calldata assets,
    uint256 errorCodeBreakpoint
)
    external
    returns (AccountSnapshot[] memory, uint256[] memory, uint256);
```

**Inputs:**

<table><thead><tr><th width="205">Type</th><th width="207">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>address</code></td><td><code>account</code></td><td>Account to snapshot.</td></tr><tr><td><code>address[] calldata</code></td><td><code>assets</code></td><td>cToken addresses to price.</td></tr><tr><td><code>uint256</code></td><td><code>errorCodeBreakpoint</code></td><td>Error code threshold that causes a revert.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="250">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>AccountSnapshot[] memory</code></td><td>Updated cToken snapshots for <code>account</code>.</td></tr><tr><td><code>uint256[] memory</code></td><td>Prices aligned to <code>assets</code>.</td></tr><tr><td><code>uint256</code></td><td>Number of assets processed.</td></tr></tbody></table>

{% hint style="info" %}
Calls `getSnapshotUpdated(account)` on each asset. Prices collateral snapshots in cToken share terms and debt snapshots in underlying terms.
{% endhint %}

***

### Support and Status Reads

#### **getPricingAdaptors()**

**Description:** Returns the adaptor addresses currently configured for an asset.

**Function signature:**

```solidity
function getPricingAdaptors(
    address asset
) external view returns(address[] memory result);
```

**Inputs:**

<table><thead><tr><th width="120">Type</th><th width="107">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>address</code></td><td><code>asset</code></td><td>Underlying asset whose pricing adaptors should be returned.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="193">Type</th><th width="119">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>address[] memory</code></td><td><code>result</code></td><td>Current adaptor dependencies for <code>asset</code>.</td></tr></tbody></table>

**Events:** None.

{% hint style="info" %}
This returns the dynamic adaptor array that the generated `assetPricingConfig()` getter omits.
{% endhint %}

***

#### **isSupportedAsset()**

**Description:** Returns whether an underlying asset or registered cToken is supported by the manager.

**Function signature:**

```solidity
function isSupportedAsset(address asset) external view returns (bool);
```

**Inputs:**

<table><thead><tr><th width="118">Type</th><th width="103">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>address</code></td><td><code>asset</code></td><td>Underlying asset or registered cToken to check.</td></tr></tbody></table>

Return data:

<table><thead><tr><th width="85">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>bool</code></td><td><code>true</code> if the asset has at least one configured pricing adaptor. For cTokens, support is checked through the registered underlying asset.</td></tr></tbody></table>

**Events:** None.

***

#### **isSequencerValid()**

**Description:** Returns whether the optional central-registry uptime-feed check currently permits pricing.

**Function signature:**

```solidity
function isSequencerValid() external view returns (bool);
```

**Inputs:** None.

**Return data:**

<table><thead><tr><th width="90">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>bool</code></td><td><code>true</code> when no uptime feed is configured or the configured feed currently passes the manager's check.</td></tr></tbody></table>

**Events:** None.

{% hint style="info" %}
`getPrice()` returns `(0, BAD_SOURCE)` when this check fails. Market helper functions revert with `OracleManager__ErrorCodeFlagged()` when this check fails.
{% endhint %}

***

#### **isApprovedAdaptor()**

**Description:** Returns whether an adaptor address is approved for manager use.

**Function signature:**

```solidity
function isApprovedAdaptor(address adaptor) external view returns (bool);
```

**Inputs:**

<table><thead><tr><th width="115">Type</th><th width="106">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>address</code></td><td><code>adaptor</code></td><td>Adaptor address to check.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="86">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>bool</code></td><td><code>true</code> if the adaptor is approved for manager use.</td></tr></tbody></table>

**Events:** None.

***

#### **cTokens()**

**Description:** Returns the underlying asset for a registered Curvance cToken, or `address(0)` if the token is not registered.

**Function signature:**

```solidity
function cTokens(address cToken) external view returns (address);
```

**Inputs:**

<table><thead><tr><th width="113">Type</th><th width="103">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>address</code></td><td><code>cToken</code></td><td>Curvance cToken address to inspect.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="110">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>address</code></td><td>Registered underlying asset, or <code>address(0)</code> when <code>cToken</code> is not registered.</td></tr></tbody></table>

**Events:** None.

{% hint style="info" %}
`OracleManager` uses this mapping to decide whether `getPrice()` should price the input as a cToken share.
{% endhint %}

***

#### **assetPricingConfig()**

**Description:** Returns the stored deviation-bound fields for an asset. The generated getter does not return the dynamic adaptor array; use `getPricingAdaptors(asset)` for adaptor addresses.

**Function signature:**

```solidity
function assetPricingConfig(address asset) external view returns (
    uint16,
    uint16,
    uint16,
    uint16
);
```

**Inputs:**

<table><thead><tr><th width="115">Type</th><th width="95">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>address</code></td><td><code>asset</code></td><td>Underlying asset whose stored deviation bounds should be read.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="120">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>uint16</code></td><td>Stored USD bad-source bound.</td></tr><tr><td><code>uint16</code></td><td>Stored USD caution bound.</td></tr><tr><td><code>uint16</code></td><td>Stored native-token bad-source bound.</td></tr><tr><td><code>uint16</code></td><td>Stored native-token caution bound.</td></tr></tbody></table>

**Events:** None.

{% hint style="info" %}
The stored values include the `BPS` offset added by `_setDeviationBounds()`.  Use `getPricingAdaptors(asset)` to read the configured adaptor addresses.
{% endhint %}

***

#### **centralRegistry()**

**Description:** Returns the central registry used for permission checks and optional uptime-oracle lookup.

**Function signature:**

```solidity
function centralRegistry() external view returns (ICentralRegistry);
```

**Inputs:** None.

**Return data:**

<table><thead><tr><th width="205">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>ICentralRegistry</code></td><td>Central registry used by the manager.</td></tr></tbody></table>

**Events:** None.

***

#### **native()**

**Description:** Returns the sentinel address used for the chain's native gas token.

**Function signature:**

```solidity
function native() external view returns (address);
```

**Inputs:** None.

**Return data:**

<table><thead><tr><th width="118">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>address</code></td><td>Sentinel address used to price the chain's native gas token.</td></tr></tbody></table>

**Events:** None.

{% hint style="info" %}
The sentinel is `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`.
{% endhint %}

***

### **Constant Getters**

**Description:** Return immutable oracle-manager configuration limits.

**Function signatures:**

```solidity
function GRACE_PERIOD_TIME() external view returns (uint256);
function MAX_DEVIATION_BOUND() external view returns (uint256);
function MIN_DEVIATION_BOUND() external view returns (uint256);
function MIN_CAUTION_TO_BAD_SOURCE_DELTA() external view returns (uint256);
```

**Inputs:** None.

**Return data:**

| Function                            | Type      | Description                                                |
| ----------------------------------- | --------- | ---------------------------------------------------------- |
| `GRACE_PERIOD_TIME()`               | `uint256` | Uptime-feed grace period, in seconds.                      |
| `MAX_DEVIATION_BOUND()`             | `uint256` | Maximum allowed deviation bound, in BPS.                   |
| `MIN_DEVIATION_BOUND()`             | `uint256` | Minimum allowed deviation bound, in BPS.                   |
| `MIN_CAUTION_TO_BAD_SOURCE_DELTA()` | `uint256` | Minimum gap between caution and bad-source bounds, in BPS. |


# Price Guard

### Overview

Price guards are oracle-side bounds that Curvance can apply before a price is returned to protocol consumers. They are designed for pegged or reference-priced assets where a bounded price range is part of the market configuration, such as stablecoins or liquid staking assets priced against a reference asset.

A guard has a minimum price and a maximum price. If the observed price falls below the minimum, the guard returns `0` so the pricing path can treat the read as an error. If the observed price is above the maximum, the guard returns the maximum. If the observed price is inside the range, the guard returns the observed price.

Guards can be static or dynamic:

* **Static guards** use fixed `minPrice` and `basePrice` values.
* **Dynamic guards** increase both bounds over time from `timestampStart` using `ips`, a WAD-scaled per-second growth rate.

### Where Guards Live

Price guard state lives in two source locations.

#### Adaptor Guards

`BaseOracleAdaptor` stores guards in `priceGuards[asset][inUSD]`. This means each supported asset can have separate guard configuration for USD-denominated prices and native-token-denominated prices.

Adaptor guards are applied after `_adjustPrice()` normalizes the incoming feed value to WAD precision. In this path, `minPrice` and `basePrice` should be configured in the same 18-decimal price scale that the adaptor returns.

#### Combined Aggregator Guards

`CombinedAggregator` stores one guard in `pg`. This guard applies only to `secondaryAggregator`.

The combined answer is computed from the primary feed and the guarded secondary answer. The secondary guard is applied before the multiplication with the primary feed. `CombinedAggregator` does not provide a separate guard for the primary feed.

For this path, `minPrice` and `basePrice` are in the same units as the secondary feed answer, not the final combined WAD-normalized adaptor price.

### PriceGuard Struct Fields

`IOracleAdaptor.PriceGuard` is the shared struct used by both guard locations.

```solidity
struct PriceGuard {
    uint40 timestampStart;
    uint40 ips;
    uint88 basePrice;
    uint88 minPrice;
}
```

<table><thead><tr><th width="201">Field</th><th>Meaning</th></tr></thead><tbody><tr><td><code>timestampStart</code></td><td>The timestamp used as the dynamic growth start. Static guards require this to be <code>0</code>.</td></tr><tr><td><code>ips</code></td><td>Increase per second, WAD-scaled. <code>0</code> means the guard is static.</td></tr><tr><td><code>basePrice</code></td><td>The starting maximum price. For static guards, this is the fixed maximum. <code>0</code> means the guard is disabled.</td></tr><tr><td><code>minPrice</code></td><td>The starting minimum price. For static guards, this is the fixed minimum.</td></tr></tbody></table>

### Guard Validation

#### Adaptor Guard Validation

`BaseOracleAdaptor.setGuardedPriceConfig()` validates the requested guard before storage is updated:

1. The caller must pass the market-permission check.
2. If `ips == 0`, `timestampStart` must be `0`.
3. If `ips > 0`, `timestampStart` must be nonzero, not in the future, and at least seven days before the current block timestamp.
4. `ips` must fit in `uint40`.
5. `basePrice` must be nonzero and fit in `uint88`.
6. `minPrice` must be less than or equal to `basePrice`.
7. The asset must price successfully for the requested denomination through both `getPrice(asset, inUSD, false)` and `getPrice(asset, inUSD, true)`.
8. The returned price denomination must match the requested `inUSD` value.
9. The guarded current minimum cannot be above the current price used in the final validation read.
10. If an existing dynamic guard has a later `timestampStart`, a new nonzero `timestampStart` cannot move it backward.

After validation, the adaptor stores the packed field values and emits `PriceGuardUpdated(pg)`.

#### Combined Aggregator Guard Validation

`CombinedAggregator.setGuardedPriceConfig()` applies the same timestamp, type-width, nonzero `basePrice`, and `minPrice <= basePrice` checks. It then validates the secondary feed directly:

1. The caller must pass the market-permission check.
2. `secondaryAggregator.latestRoundData()` must return a positive answer.
3. The secondary feed update must be inside `secondaryHeartbeat`.
4. The guarded current minimum cannot be above the current secondary answer.
5. If an existing dynamic guard has a later `timestampStart`, a new nonzero `timestampStart` cannot move it backward.

After validation, the aggregator stores the packed field values and emits `PriceGuardUpdated(pg)`.

### Runtime Guard Behavior

When no guard is active, `basePrice == 0` and the price is returned unchanged.

For static guards, where `ips == 0`:

1. If `price < minPrice`, the guard returns `0`.
2. If `price > basePrice`, the guard returns `basePrice`.
3. Otherwise, the guard returns `price`.

For dynamic guards, both bounds grow using the same formula:

```solidity
guarded = price * ((timePassed * ips) + WAD) / WAD;
```

`timePassed` is `block.timestamp - timestampStart`. The formula is applied separately to `minPrice` and `basePrice`.

At runtime:

1. The dynamic minimum is calculated from `minPrice`.
2. If the observed price is below the dynamic minimum, the guard returns `0`.
3. The dynamic maximum is calculated from `basePrice`.
4. If the observed price is above the dynamic maximum, the guard returns the dynamic maximum.
5. Otherwise, the observed price is returned.

In `CombinedAggregator`, this guarded secondary answer is used in `latestRoundData()` and `getAdjustedAnswer(int256 answer)` before the final combined answer is returned. If the secondary feed is stale, `latestRoundData()` sets `updatedAt` to `0`, while `getAdjustedAnswer()` returns `0`.

### Configuration and Disable Flows

Adaptor guards are configured per `(asset, inUSD)` pair:

```solidity
function setGuardedPriceConfig(
    address asset,
    bool inUSD,
    uint256 timestampStart,
    uint256 ips,
    uint256 basePrice,
    uint256 minPrice
) external;
```

Adaptor guards are disabled per `(asset, inUSD)` pair:

```solidity
function disableGuardedPriceConfig(address asset, bool inUSD) external;
```

Combined aggregator guards are configured once for the secondary feed:

```solidity
function setGuardedPriceConfig(
    uint256 timestampStart,
    uint256 ips,
    uint256 basePrice,
    uint256 minPrice
) external;
```

Combined aggregator guards are disabled by deleting `pg`:

```solidity
function disableGuardedPriceConfig() external;
```

Do not try to disable a guard by calling `setGuardedPriceConfig()` with `basePrice == 0`. The setter rejects a zero `basePrice`. Use the disable function for the relevant guard location.

When an adaptor asset is removed through `removeAsset(address asset)`, `BaseOracleAdaptor` deletes the supported-asset flag, wipes asset-specific config, disables both the USD and native-token guards, notifies the Oracle Manager, and emits `AssetRemoved(asset)`.

### Permission Model

Guard configuration and disable functions use `centralRegistry.hasMarketPermissions(msg.sender)`. Callers without market permissions revert with the local unauthorized error.

`BaseOracleAdaptor.removeAsset(address asset)` uses `centralRegistry.hasElevatedPermissions(msg.sender)` because it removes adaptor support for the asset, not only guard configuration.

Read functions do not require a privileged caller.

### Integration Considerations

Do not assume every asset has a price guard. A guard with `basePrice == 0` is disabled.

Treat a guarded return value of `0` as a pricing failure signal. It means the observed price was below the configured minimum. In `CombinedAggregator`, `getAdjustedAnswer()` also returns `0` when the secondary feed is stale, while `latestRoundData()` signals that stale secondary feed by returning `updatedAt = 0`.

For adaptor guards, read and configure the USD and native-token guard slots independently. A guard stored for `inUSD == true` does not protect the `inUSD == false` path.

For combined aggregators, the guard is specific to the secondary feed. It does not isolate changes in the primary feed.

When preparing dynamic guard updates, validate the timestamp before submitting the transaction. The contract requires a nonzero `timestampStart` that is not in the future and is at least seven days before the current block timestamp.

Use real asset and aggregator addresses from the deployment registry for the chain you are integrating with. Documentation examples should use placeholders such as `0x...`.

### User Interaction Functions

#### Read Guarded Adaptor Price

#### **getPrice()**

**Description:** Returns adaptor pricing data for a supported asset. Where the adaptor implementation uses the `BaseOracleAdaptor` price adjustment helper, the returned price reflects any active guard for the requested asset and denomination.

**Contract:** `IOracleAdaptor` / `BaseOracleAdaptor`

**Function signature:**

```solidity
function getPrice(
    address asset,
    bool inUSD,
    bool getLower
) external view returns (PricingResult memory);
```

**Inputs:**

<table><thead><tr><th width="108">Type</th><th width="99">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>address</code></td><td><code>asset</code></td><td>The asset to price.</td></tr><tr><td><code>bool</code></td><td><code>inUSD</code></td><td><code>true</code> for a USD-denominated price, <code>false</code> for a native-token-denominated price.</td></tr><tr><td><code>bool</code></td><td><code>getLower</code></td><td>Selector used by adaptor implementations that support multiple price paths.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="170">Type</th><th width="91">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>PricingResult</code></td><td>None</td><td>Struct containing <code>price</code>, <code>inUSD</code>, and <code>hadError</code>.</td></tr></tbody></table>

**Events:** None.

{% hint style="info" %}
`OracleAdaptor.getPrice()` checks support for `asset`, then delegates to the adaptor implementation's internal `_getPrice()` path.
{% endhint %}

### Read Adaptor Guard Config

#### **getPriceGuard()**

**Description:** Returns the configured `PriceGuard` for an adaptor asset and denomination.

**Contract:** `IOracleAdaptor` / `BaseOracleAdaptor`

**Function signature:**

```solidity
function getPriceGuard(
    address asset,
    bool inUSD
) external view returns (PriceGuard memory);
```

**Inputs:**

<table><thead><tr><th width="109">Type</th><th width="97">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>address</code></td><td><code>asset</code></td><td>The asset whose guard should be read.</td></tr><tr><td><code>bool</code></td><td><code>inUSD</code></td><td><code>true</code> for the USD-denominated guard, <code>false</code> for the native-token-denominated guard.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="132">Type</th><th width="87">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>PriceGuard</code></td><td>None</td><td>The stored guard. <code>basePrice == 0</code> means disabled.</td></tr></tbody></table>

**Events:** None.

#### **priceGuards()**

**Description:** Compiler-generated getter for the `BaseOracleAdaptor.priceGuards` public mapping.

{% hint style="info" %}
This is the interface-safe way to read adaptor guard data.
{% endhint %}

**Contract:** `BaseOracleAdaptor`

**Function signature:**

```solidity
function priceGuards(
    address asset,
    bool inUSD
)
    external
    view
    returns (
        uint40 timestampStart,
        uint40 ips,
        uint88 basePrice,
        uint88 minPrice
    );
```

**Inputs:**

<table><thead><tr><th width="117">Type</th><th width="102">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>address</code></td><td><code>asset</code></td><td>The asset whose guard should be read.</td></tr><tr><td><code>bool</code></td><td><code>inUSD</code></td><td><code>true</code> for the USD-denominated guard, <code>false</code> for the native-token-denominated guard.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="107">Type</th><th width="170">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>uint40</code></td><td><code>timestampStart</code></td><td>Dynamic growth start timestamp, or <code>0</code> for static guards.</td></tr><tr><td><code>uint40</code></td><td><code>ips</code></td><td>WAD-scaled per-second growth rate.</td></tr><tr><td><code>uint88</code></td><td><code>basePrice</code></td><td>Starting maximum guard price. <code>0</code> means disabled.</td></tr><tr><td><code>uint88</code></td><td><code>minPrice</code></td><td>Starting minimum guard price.</td></tr></tbody></table>

**Events**: None.

{% hint style="info" %}
Prefer `getPriceGuard()` when integrating through `IOracleAdaptor`.
{% endhint %}

### Read Combined Aggregator Guard

### **pg()**

**Description:** Compiler-generated getter for the `CombinedAggregator.pg` public struct.

**Contract:** `CombinedAggregator`

**Function signature:**

```solidity
function pg()
    external
    view
    returns (
        uint40 timestampStart,
        uint40 ips,
        uint88 basePrice,
        uint88 minPrice
    );
```

**Inputs:** None.

**Return data:**

<table><thead><tr><th width="117">Type</th><th width="174">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>uint40</code></td><td><code>timestampStart</code></td><td>Dynamic growth start timestamp, or <code>0</code> for static guards.</td></tr><tr><td><code>uint40</code></td><td><code>ips</code></td><td>WAD-scaled per-second growth rate.</td></tr><tr><td><code>uint88</code></td><td><code>basePrice</code></td><td>Starting maximum guard price. <code>0</code> means disabled.</td></tr><tr><td><code>uint88</code></td><td><code>minPrice</code></td><td>Starting minimum guard price.</td></tr></tbody></table>

**Events:** None.

{% hint style="info" %}
This getter returns struct fields as separate return values.
{% endhint %}

### Guarded Combined Price Reads

#### **latestRoundData()**

**Description:** Returns the latest combined aggregator round data with the secondary guard applied to the secondary answer.

**Contract:** `CombinedAggregator`

**Function signature:**

```solidity
function latestRoundData()
    external
    view
    returns (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
    );
```

**Inputs:** None.

**Return data:**

<table><thead><tr><th width="114">Type</th><th width="170">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>uint80</code></td><td><code>roundId</code></td><td>Round ID from the primary aggregator.</td></tr><tr><td><code>int256</code></td><td><code>answer</code></td><td>Combined answer after applying the secondary guard.</td></tr><tr><td><code>uint256</code></td><td><code>startedAt</code></td><td>Timestamp from the primary aggregator.</td></tr><tr><td><code>uint256</code></td><td><code>updatedAt</code></td><td>Timestamp from the primary aggregator, or <code>0</code> when the secondary feed is stale.</td></tr><tr><td><code>uint80</code></td><td><code>answeredInRound</code></td><td>Round value from the primary aggregator.</td></tr></tbody></table>

**Events:** None.

{% hint style="info" %}
Round metadata comes from the primary feed and should not be used as proof that the secondary feed is fresh.
{% endhint %}

#### **`getAdjustedAnswer()`**

**Description:** Applies the secondary feed and its guard to an input primary answer.

**Contract:** `CombinedAggregator`

**Function signature:**

```solidity
function getAdjustedAnswer(
    int256 answer
) public view returns (int256 result);
```

**Inputs:**

<table><thead><tr><th width="115">Type</th><th width="97">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>int256</code></td><td><code>answer</code></td><td>Primary answer to combine with the guarded secondary answer.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="95">Type</th><th width="95">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>int256</code></td><td><code>result</code></td><td>Combined adjusted answer, or <code>0</code> when the secondary feed is stale or the guard returns <code>0</code>.</td></tr></tbody></table>

**Events:** None.

{% hint style="info" %}
This is a read-only helper used to apply the same secondary guard behavior outside `latestRoundData()`.
{% endhint %}


# Adaptors

### Overview

Oracle adaptors are the contract boundary between Curvance markets and external price sources. The `OracleManager` calls an adaptor through the `IOracleAdaptor` interface, receives a normalized `PricingResult`, and decides how that price should be consumed by market logic.

The contracts in this page cover three related roles:

* **`BaseOracleAdaptor`:** shared support, permission checks, price guards, normalization, and asset removal.
* **Direct feed adaptors:** `ChainlinkAdaptor` and `RedstoneClassicAdaptor`, which read round-based feeds and normalize answers.
* **Derived asset adaptors and wrapped aggregators:** Pendle token adaptors and feed-compatible wrappers that turn an existing feed into a price for a related asset.

Real asset and feed addresses are deployment-specific. Use the deployment registry for the chain you are integrating with instead of hardcoding example addresses.

### Adaptor Categories

#### Direct feed adaptors

`ChainlinkAdaptor` and `RedstoneClassicAdaptor` configure one feed per asset and denomination. A denomination is selected by the `inUSD` flag:

* `true`: the feed prices the asset in USD.
* `false`: the feed prices the asset in the deployment's native token.

If the requested denomination is not configured, each direct feed adaptor checks the opposite denomination and returns the actual denomination in `PricingResult.inUSD`.

#### Pendle token adaptors

`PendlePrincipalTokenAdaptor` prices Pendle Principal Tokens by combining:

* a Pendle TWAP-derived asset rate, and
* the quote asset price returned by `OracleManager.getPrice(config.quoteAsset, inUSD, getLower)`.

Both adaptors require the quote asset to already be supported by the `OracleManager`.

#### Wrapped aggregators

Wrapped aggregators expose feed-compatible functions such as `latestRoundData()` and `decimals()`. They are not `IOracleAdaptor` implementations. They are configured as aggregator inputs for a direct feed adaptor.

`BaseWrappedAggregator` reads an underlying aggregator and lets child contracts adjust the answer. The covered child contracts are:

* `CombinedAggregator`: multiplies a primary feed answer by a secondary feed answer.
* `VaultAggregator`: adjusts an underlying asset feed by an ERC4626 vault exchange rate.
* `WstETHAggregator`: adjusts an stETH feed by the current wstETH to stETH conversion.
* `PendlePTAggregator`: adjusts an underlying asset feed by a time-to-expiry Principal Token discount model.

### Shared Data Types

#### `PricingResult`

`IOracleAdaptor.PricingResult` is returned by `getPrice()`.

```solidity
struct PricingResult {
    uint256 price;
    bool inUSD;
    bool hadError;
}
```

* `price`: normalized price value.
* `inUSD`: `true` when `price` is denominated in USD, `false` when it is denominated in the deployment's native token.
* `hadError`: `true` when the adaptor could not return a usable price.

#### `PriceGuard`

`IOracleAdaptor.PriceGuard` stores optional minimum and maximum bounds for an asset price.

```solidity
struct PriceGuard {
    uint40 timestampStart;
    uint40 ips;
    uint88 basePrice;
    uint88 minPrice;
}
```

* `timestampStart`: when dynamic guard scaling starts.
* `ips`: increase per second, in WAD.
* `basePrice`: maximum guarded price before dynamic scaling.
* `minPrice`: minimum accepted price before dynamic scaling.

If `basePrice` is zero, the guard is disabled. If `ips` is zero, the guard is static and `timestampStart` must be zero. If `ips` is nonzero, `timestampStart` must be nonzero, cannot be in the future, and must be at least seven days before the current block timestamp.

### `BaseOracleAdaptor` Behavior

`BaseOracleAdaptor` stores the shared `centralRegistry`, the adaptor type, supported asset flags, and price guard configuration.

```solidity
mapping(address => bool) public isSupportedAsset;
mapping(address => mapping(bool => PriceGuard)) public priceGuards;
```

The base `getPrice()` path checks `isSupportedAsset[asset]` before calling the child adaptor's `_getPrice(asset, inUSD)`.

```solidity
function getPrice(
    address asset,
    bool inUSD,
    bool getLower
) external view virtual override returns (PricingResult memory result);
```

The base implementation ignores `getLower`. Pendle adaptors override `getPrice()` because they pass `getLower` through to the `OracleManager` when pricing the quote asset.

#### Price guards

Price guards are applied after the raw feed answer is normalized to WAD scale. The adjustment logic:

1. Returns the price unchanged if no guard is configured.
2. Returns zero if the price is below the configured minimum.
3. Caps the price at `basePrice` for static guards.
4. For dynamic guards, increases both `minPrice` and `basePrice` over time using `ips`.

A zero adjusted price is treated as an error by feed paths that validate the adjusted answer.

#### Asset removal

`removeAsset()` requires elevated permissions. It deletes the supported flag, wipes child-specific asset config, disables both USD and native-token price guards, calls `notifyFeedRemoval(asset)` on the `OracleManager`, and emits `AssetRemoved(asset)`.

### Chainlink and Redstone Classic Adaptors

`ChainlinkAdaptor` and `RedstoneClassicAdaptor` share the same configuration model. Each stores config by asset and denomination.

#### Chainlink `AssetConfig`

```solidity
struct AssetConfig {
    bool isConfigured;
    IChainlink aggregatorProxy;
    uint8 decimals;
    uint24 heartbeat;
}
```

`ChainlinkAdaptor.addAsset()` stores the aggregator proxy, reads its decimals, stores the heartbeat, marks the asset as supported, and emits `AssetAdded(asset, config, isUpdate)`.

```solidity
function addAsset(
    address asset,
    bool inUSD,
    address aggregatorProxy,
    uint256 heartbeat
) external;
```

#### Redstone Classic `AssetConfig`

```solidity
struct AssetConfig {
    bool isConfigured;
    IRedstone feedProxy;
    uint8 decimals;
    uint24 heartbeat;
}
```

`RedstoneClassicAdaptor.addAsset()` validates that the provided string `id` matches `IRedstone(feedProxy).getDataFeedId()` before storing the feed config.

```solidity
function addAsset(
    address asset,
    bool inUSD,
    address feedProxy,
    uint256 heartbeat,
    string memory id
) external;
```

#### Feed validation

Both direct feed adaptors:

* require elevated permissions for `addAsset()`;
* reject `address(0)` assets;
* add `HEARTBEAT_GRACE_PERIOD` to nonzero heartbeat inputs;
* reject heartbeats above `DEFAULT_HEARTBEAT`;
* call `latestRoundData()` on the configured feed;
* return `hadError = true` if the feed answer is less than or equal to zero;
* normalize the positive answer to WAD scale before applying price guards;
* set `hadError` based on stale or future-dated update timestamps.

### Pendle Token Adaptors

Pendle token adaptors compose a Pendle rate with an already supported quote asset price.

#### Pendle LP `AssetConfig`

```solidity
struct AssetConfig {
    address pt;
    uint32 twapDuration;
    address quoteAsset;
    uint8 quoteAssetDecimals;
}
```

`PendleLPTokenAdaptor.addAsset()` validates that the market's PT matches `config.pt`, the TWAP duration is at least `MINIMUM_TWAP_DURATION`, the market's standardized yield asset matches `config.quoteAsset`, the PT oracle state is usable for the TWAP duration, and the quote asset is supported by the `OracleManager`.

```solidity
function addAsset(address asset, AssetConfig memory config) external;
```

The LP price path reads `IPMarket(asset).getLpToAssetRate(config.twapDuration)`, prices `config.quoteAsset`, and returns:

```solidity
result.price = _adjustPrice(asset, inUSD, (price * lpRate) / WAD, 18);
```

#### Pendle Principal Token `AssetConfig`

```solidity
struct AssetConfig {
    IPMarket market;
    uint32 twapDuration;
    address quoteAsset;
    uint8 quoteAssetDecimals;
}
```

`PendlePrincipalTokenAdaptor.addAsset()` validates that the configured market's PT is the asset being added, the TWAP duration is at least `MINIMUM_TWAP_DURATION`, the standardized yield asset matches `config.quoteAsset`, the PT oracle state is usable for the TWAP duration, and the quote asset is supported by the `OracleManager`.

```solidity
function addAsset(address asset, AssetConfig memory config) external;
```

The Principal Token price path reads `config.market.getPtToAssetRate(config.twapDuration)`, prices `config.quoteAsset`, and returns:

```solidity
result.price = _adjustPrice(asset, inUSD, (price * ptRate) / WAD, 18);
```

For both Pendle adaptors, a quote asset pricing error sets `result.hadError = true`. A price guard adjustment to zero also sets `result.hadError = true`.

### Wrapped Aggregators

#### `BaseWrappedAggregator`

`BaseWrappedAggregator` implements the round-based feed surface expected by the direct feed adaptors. Its constructor validates that the underlying aggregator returns a positive answer, a nonzero `updatedAt`, and a nonzero round ID.

```solidity
function decimals() external view returns (uint8 result);

function latestRoundData()
    external
    view
    virtual
    returns (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
    );

function latestRound() external view returns (uint256 result);

function getRoundData(uint80 _roundId)
    external
    view
    returns (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
    );

function getDataFeedId() external view returns (bytes32 result);

function underlyingAggregator() public view returns (IChainlink result);

function getAdjustedAnswer(int256 answer)
    public
    view
    virtual
    returns (int256 result);
```

`latestRoundData()` and `getRoundData()` read the underlying aggregator and then call `getAdjustedAnswer(answer)`.

#### `CombinedAggregator`

`CombinedAggregator` combines a primary aggregator with a secondary aggregator. It validates the secondary aggregator during construction, stores a `secondaryHeartbeat`, and multiplies the primary answer by the secondary answer divided by the secondary feed's decimal precision.

```solidity
function latestRoundData()
    external
    view
    override
    returns (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
    );

function getAdjustedAnswer(int256 answer)
    public
    view
    virtual
    override
    returns (int256 result);

function setGuardedPriceConfig(
    uint256 timestampStart,
    uint256 ips,
    uint256 basePrice,
    uint256 minPrice
) external;

function disableGuardedPriceConfig() external;

function setSecondaryHeartbeat(uint256 heartbeat) external;
```

The `CombinedAggregator` guard applies only to the secondary answer before it is multiplied into the primary answer. The primary feed is validated by the adaptor that reads the combined aggregator.

If the secondary feed is stale or future-dated, `latestRoundData()` sets `updatedAt = 0`, and `getAdjustedAnswer()` returns zero. The direct feed adaptor then treats the wrapped answer as unusable.

#### `VaultAggregator`

`VaultAggregator` adjusts an underlying asset feed by the vault exchange rate.

```solidity
function getAdjustedAnswer(int256 answer)
    public
    view
    virtual
    override
    returns (int256 result);
```

The exchange rate is read with:

```solidity
IERC4626(vault).convertToAssets(10 ** IERC4626(vault).decimals())
```

The constructor validates that the vault's `asset()` matches the configured underlying asset.

#### `WstETHAggregator`

`WstETHAggregator` is a specialized `VaultAggregator`. It validates that the wrapped token's `stETH()` address matches the configured underlying asset and uses `getStETHByWstETH(WAD)` as the exchange rate.

#### `PendlePTAggregator`

`PendlePTAggregator` applies a deterministic time-to-expiry discount to an underlying asset feed. The constructor:

* converts `_discountOneYearBPS` from BPS to WAD;
* rejects a zero discount or a discount above WAD;
* validates the Principal Token's standardized yield asset against the configured underlying asset;
* rejects configurations where the Principal Token expires more than one year after deployment.

Before expiry, the exchange rate is:

```solidity
WAD - ((timeToExpiry * _discountOneYear * WAD) / (SECONDS_PER_YEAR * WAD))
```

At or after expiry, the exchange rate is `WAD`, pricing the Principal Token one-to-one with its underlying asset.

### Data Normalization

Adaptor prices are normalized to WAD scale before they are returned to the caller. Direct feed adaptors read feed decimals from the configured feed and normalize with:

```solidity
price = FixedPointMathLib.fullMulDiv(price, WAD, 10 ** decimals);
```

Pendle token adaptors compose a WAD-scaled Pendle rate with the quote asset's WAD-scaled price, then call `_adjustPrice(..., 18)`.

Wrapped aggregators preserve the underlying feed's decimals through `decimals()` and adjust the feed answer inside `latestRoundData()` or `getAdjustedAnswer()`. When a wrapped aggregator is configured inside a direct feed adaptor, the direct feed adaptor still performs its own normalization and heartbeat validation.

### Permissions

Read functions are permissionless.

Elevated permissions from `centralRegistry.hasElevatedPermissions(msg.sender)` are required for:

* `ChainlinkAdaptor.addAsset()`
* `RedstoneClassicAdaptor.addAsset()`
* `PendleLPTokenAdaptor.addAsset()`
* `PendlePrincipalTokenAdaptor.addAsset()`
* `BaseOracleAdaptor.removeAsset()`

Market permissions from `centralRegistry.hasMarketPermissions(msg.sender)` are required for:

* `BaseOracleAdaptor.setGuardedPriceConfig()`
* `BaseOracleAdaptor.disableGuardedPriceConfig()`
* `CombinedAggregator.setGuardedPriceConfig()`
* `CombinedAggregator.disableGuardedPriceConfig()`
* `CombinedAggregator.setSecondaryHeartbeat()`

### Integration Considerations

* Treat `hadError = true` as an unusable price result.
* Check `PricingResult.inUSD` instead of assuming the requested denomination was returned.
* Source real adaptor, aggregator, and asset addresses from the deployment registry.
* For direct feed adaptors, configure the feed in the adaptor before registering the adaptor with the `OracleManager`.
* For Pendle adaptors, ensure the quote asset is already supported by the `OracleManager`.
* For Pendle TWAPs, call the relevant Pendle observation setup before adding the asset if the PT oracle reports that increased cardinality is required.
* For wrapped aggregators, remember that the wrapper is read by a direct feed adaptor. The wrapper is not registered as an `IOracleAdaptor`.
* For `CombinedAggregator`, configure any secondary-answer guard on the combined aggregator itself and any final composed-price guard on the direct feed adaptor that reads it.

## User Interaction Functions

### Shared adaptor reads

#### **getPrice()**

**Description:** Prices a supported asset and returns the normalized price result.

**Contract:** `IOracleAdaptor`, implemented by `BaseOracleAdaptor` and overridden by the Pendle adaptors.

**Function signature:**

```solidity
function getPrice(
    address asset,
    bool inUSD,
    bool getLower
) external view returns (PricingResult memory);
```

**Inputs:**

<table><thead><tr><th width="113">Type</th><th width="108">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>address</code></td><td><code>asset</code></td><td>Asset to price.</td></tr><tr><td><code>bool</code></td><td><code>inUSD</code></td><td>Requested denomination.</td></tr><tr><td><code>bool</code></td><td><code>getLower</code></td><td>Selector passed through by Pendle adaptors when pricing quote assets.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="173">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>PricingResult</code></td><td>Price, denomination flag, and error flag.</td></tr></tbody></table>

**Events:** None.

{% hint style="info" %}
Reverts if `asset` is not supported. The base implementation ignores `getLower`.
{% endhint %}

#### **getPriceGuard()**

**Description:** Reads the configured price guard for an asset and denomination.

**Contract:** `BaseOracleAdaptor`

**Function signature:**

```solidity
function getPriceGuard(
    address asset,
    bool inUSD
) external view returns (PriceGuard memory result);
```

**Inputs:**

<table><thead><tr><th width="99">Type</th><th width="107">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>address</code></td><td><code>asset</code></td><td>Asset to inspect.</td></tr><tr><td><code>bool</code></td><td><code>inUSD</code></td><td>Denomination of the guard to inspect.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="151">Type</th><th width="128">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>PriceGuard</code></td><td><code>result</code></td><td>Current guard fields.</td></tr></tbody></table>

**Events:** None.

#### **isSupportedAsset()**

**Description:** Returns whether the adaptor currently supports an asset.

**Contract:** `IOracleAdaptor`, implemented by `BaseOracleAdaptor`

**Function signature:**

```solidity
function isSupportedAsset(address asset) external view returns (bool);
```

**Inputs:**

<table><thead><tr><th width="135">Type</th><th width="100">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>address</code></td><td><code>asset</code></td><td>Asset to inspect.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="100">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>bool</code></td><td>Whether the adaptor supports the asset.</td></tr></tbody></table>

**Events:** None.

#### **adaptorType()**

**Description:** Returns the adaptor type value derived from the adaptor name.

**Contract:** `BaseOracleAdaptor`

**Function signature:**

```solidity
function adaptorType() external view returns (uint256 result);
```

**Inputs:** None.

**Return data:**

<table><thead><tr><th width="119">Type</th><th width="116">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>uint256</code></td><td><code>result</code></td><td>Adaptor type hash value.</td></tr></tbody></table>

**Events:** None.

### Wrapped aggregator reads

#### **latestRoundData()**

**Description:** Reads the underlying aggregator and returns the adjusted answer.

**Contract:** `BaseWrappedAggregator`, overridden by `CombinedAggregator`

**Function signature:**

```solidity
function latestRoundData()
    external
    view
    returns (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
    );
```

**Inputs:** None.

**Return data:**

<table><thead><tr><th width="140">Type</th><th width="164">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>uint80</code></td><td><code>roundId</code></td><td>Round ID from the underlying or primary aggregator.</td></tr><tr><td><code>int256</code></td><td><code>answer</code></td><td>Adjusted answer.</td></tr><tr><td><code>uint256</code></td><td><code>startedAt</code></td><td>Round start timestamp.</td></tr><tr><td><code>uint256</code></td><td><code>updatedAt</code></td><td>Round update timestamp.</td></tr><tr><td><code>uint80</code></td><td><code>answeredInRound</code></td><td>Round in which the answer was computed.</td></tr></tbody></table>

**Events:** None.

{% hint style="info" %}
`CombinedAggregator` combines two feeds, so round metadata should not be used as the source of correctness for the composed answer.
{% endhint %}

#### **getAdjustedAnswer()**

**Description:** Returns the adjusted answer for a supplied underlying answer.

**Contract:** `BaseWrappedAggregator` children

**Function signature:**

```solidity
function getAdjustedAnswer(int256 answer)
    public
    view
    returns (int256 result);
```

**Inputs:**

<table><thead><tr><th width="102">Type</th><th width="104">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>int256</code></td><td><code>answer</code></td><td>Underlying feed answer to adjust.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="113">Type</th><th width="116">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>int256</code></td><td><code>result</code></td><td>Adjusted answer.</td></tr></tbody></table>

**Events:** None.

Notes:

{% hint style="info" %}
Child contracts define the exchange-rate or composition logic.
{% endhint %}

#### **underlyingAggregator()**

**Description:** Returns the underlying aggregator read by the wrapper.

**Contract:** `BaseWrappedAggregator`

**Function signature:**

```solidity
function underlyingAggregator() public view returns (IChainlink result);
```

**Inputs:** None.

**Return data:**

<table><thead><tr><th width="145">Type</th><th width="113">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>IChainlink</code></td><td><code>result</code></td><td>Underlying aggregator.</td></tr></tbody></table>

**Events:** None.


# Plugin & Delegation System

{% hint style="danger" %}
**Be Careful When Delegating Actions**

When you grant delegation permissions to an external address or contract, you are authorizing that entity to perform actions on your behalf within the Curvance Protocol. This permission should only be granted to thoroughly vetted and trusted entities.

**Potential Risks**

* **Financial Control:** Delegates can execute operations that directly impact your assets and positions.
* **Denial of Service**: A malicious delegate could repeatedly execute operations that delay critical actions such as asset redemption.
* **Unexpected Behavior:** Even well-intentioned delegates might behave unexpectedly if their contracts contain bugs or vulnerabilities.
* **Position Manipulation:** In leveraged positions, delegates can adjust your risk exposure through actions like leveraging and deleveraging.
  {% endhint %}

## Overview

The Curvance Plugin Architecture is a modular system that enables authorized third-party contracts or addresses to perform actions on behalf of users. This architecture enhances capital efficiency and user experience by enabling the development of automation tools, complex trading strategies, and cross-chain operations, all without requiring direct user interaction at each step.

### Core Components

The Plugin Architecture is built around three primary components:

1. **ActionRegistry:** Base library that manages user configuration for delegation and transfer permissions.
2. **PluginDelegable:** Abstract contract that implements delegate approval functionality.
3. **Central Registry:** Core hub that inherits from ActionRegistry and serves as the source of truth.

### Data Flow & State Management

#### User Configuration State Machine

Each user has a configuration record in the ActionRegistry that tracks:

```solidity
UserConfig {
    uint208 lockCooldown;               // Duration of transfer/delegation cooldown
    uint40 transferEnabledTimestamp;    // When transfers become enabled
    bool transferDisabled;              // Transfer lock status
    uint208 approvalIndex;              // Approval index for delegate revocation
    uint40 delegationEnabledTimestamp;  // When delegations become enabled  
    bool delegationDisabled;            // Delegation status
}
```

This state record facilitates two key security mechanisms:

1. **Transfer locking:** Controls whether a user's tokens can be transferred.
2. **Delegation control:** Controls whether a user can approve new delegates.

#### Delegation Approval System

Delegations are tracked in a nested mapping structure:

```
owner => approvalIndex => delegate => isApproved
```

This design creates a three-dimensional relationship:

* The token/rights **owner.**
* Their current **approval index** (a security counter).
* Each **delegate** address.
* Whether that delegate **is approved** to act on behalf of the owner.

#### Security State Transitions

**Approval Index Mechanism**

The approval index serves as a master revocation system. When a user increments their approval index:

1. All previously approved delegates are instantly revoked..
2. New delegations must be established at the new index

**Transfer & Delegation Cooldown**

The system implements protective cooldown periods:

1. **Disabled** → **Enabled**: When a user re-enables transfers or delegation capability, a cooldown period applies before the action takes effect.
2. **Cooldown Reduction**: If a user decreases their cooldown period, the system automatically enforces the previous cooldown period.

This prevents attackers from social engineering users to rapidly disable protections.

### Integration Points

Contracts that integrate with the Plugin Architecture:

1. Inherit from **PluginDelegable.**
2. Implement permission checks using **\_checkDelegate()** for delegate-initiated operations.
3. Reference the Central Registry for user configuration state.

The architecture is utilized by core protocol components including cToken contracts and position management systems, allowing for complex operations like automated liquidation protection, cross-chain rebalancing, and advanced trading strategies.

***


# Transfer Lock Mechanism

### Overview

The Transfer Lock Mechanism is a critical security component of Curvance's protection system, allowing users to control the transferability of their tokens. Operating as an optional "2FA" layer, this mechanism helps defend against phishing attempts by giving users the ability to temporarily or indefinitely disable token transfers until explicitly re-enabled.

The transfer lock system introduces a security cooldown period when transitioning from a locked to an unlocked state, adding an important time buffer that can prevent attackers from quickly gaining control of assets after compromising an account.

### Core Functions

#### setCooldown()

**Description:** Sets the duration that transfers will remain restricted after a user disables their transfer lock. This forms the core of the time-delay protection mechanism.

If a user decreases their cooldown, the previous (longer) cooldown will be automatically applied to any pending transfer unlock to prevent a phishing attack where an attacker forces a cooldown reduction.

**Contract:** CentralRegistry

**Function signature:**

```solidity
function setCooldown(uint256 cooldown) external
```

<table><thead><tr><th width="121.48333740234375">Type</th><th width="121.16668701171875">Name</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>cooldown</td><td>The length of time (in seconds) that transferability should remain restricted after a lock is disabled. Max value is 52 weeks.</td></tr></tbody></table>

**Events:**

```solidity
// Defined in ActionRegistry.sol
event CooldownSet(address indexed user, uint256 userLockCooldown);
```

***

#### checkTransfersDisabled()

**Description:** Determines whether a specific user has transfers enabled or disabled. This can be called by any contract or external account to verify a user's transfer permission status.

**Contract:** CentralRegistry

**Function signature:**

```solidity
function checkTransfersDisabled(address user) external view returns (bool)
```

<table><thead><tr><th width="111.433349609375">Type</th><th width="113.88336181640625">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>user</td><td>The address to check transfer status for.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="120.74996948242188">Type</th><th>Description</th></tr></thead><tbody><tr><td>bool</td><td>Returns true if the user has transfers disabled or if their cooldown period has not yet expired.</td></tr></tbody></table>

***

#### setTransferLockStatus()

**Description:** Enables or disables token transferability for the caller. When disabling the lock (enabling transfers), the caller's configured cooldown period will be applied.

* A user must explicitly flip their transfer lock status (can't set to the same value it already has).
* When enabling transfers, the cooldown period starts immediately.

**Contract:** CentralRegistry

**Function signature:**

```solidity
function setTransferLockStatus(bool transferDisabled) external
```

<table><thead><tr><th width="120.36669921875">Type</th><th width="185.81671142578125">Name</th><th>Description</th></tr></thead><tbody><tr><td>bool</td><td>transferDisabled</td><td>true to lock transfers (disable transferability), false to unlock transfers (enable transferability after cooldown).</td></tr></tbody></table>

Events:

```solidity
// Defined in ActionRegistry.sol
event LockStatusChanged(
    address indexed user,
    bool isLocked,
    uint256 transferEnabledTimestamp
);
```


# Delegable Actions

## Overview

Delegable Actions allow third-party contracts (plugins) to perform specific operations on behalf of users within the Curvance ecosystem. This delegation system offers a more flexible and powerful alternative to standard ERC20 approvals, enabling opportunities for automation, complex strategies, and improved user experiences.

Any Curvance contract that inherits from `PluginDelegable` can support delegate-triggered operations through functions that typically end with a for postfix (e.g., `borrowFor`, `withdrawFor`, `leverageFor`). This pattern ensures consistent delegate authorization checks across the protocol.

{% hint style="info" %}
For a full list of delegable actions, please check out our quickstart guide here: [List of Delegable Actions](/app/developer-docs/quick-start-guides/plugin-integration/list-of-delegable-actions)
{% endhint %}

The delegation system provides several security advantages over traditional approaches:

* **Granular control:** Users can grant and revoke specific action permissions to different addresses
* **Emergency revocation:** Users can revoke all delegations at once by incrementing their approval index
* **Time-lock protection:** Optional delegation lockdown with a cooldown period

Delegations exist across all Curvance contracts simultaneously, using the Central Registry to track and validate delegation permissions throughout the protocol.

***

## User Interaction Functions

### Delegation in PluginDelegable.sol

#### isDelegate()

**Description:** Determines whether a delegate address has permission to act on behalf of a specified user.

**Contract:** CentralRegistry

**Function signature:**

```solidity
function isDelegate(address user, address delegate) public view returns (bool)
```

<table><thead><tr><th width="133.76669311523438">Type</th><th width="162.066650390625">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>user</td><td>The address to check whether delegate has delegation permissions for.</td></tr><tr><td>address</td><td>delegate</td><td>The address to check delegation permissions of user.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="147.54998779296875">Type</th><th>Description</th></tr></thead><tbody><tr><td>bool</td><td>Returns true if delegate is authorized to act on behalf of user.</td></tr></tbody></table>

***

#### setDelegateApproval()

**Description**: Approves or restricts a delegate's authority to operate on the caller's behalf. This is the primary method for users to control their delegation permissions.

**Contract:** CentralRegistry

**Function signature:**

```solidity
function setDelegateApproval(address delegate, bool isApproved) external
```

<table><thead><tr><th width="141.58334350585938">Type</th><th width="169.23333740234375">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>delegate</td><td>The address that will be approved or restricted from delegated actions.</td></tr><tr><td>bool</td><td>isApproved</td><td>Whether delegate is being approved (true) or restricted (false).</td></tr></tbody></table>

Events:

```solidity
event DelegateApproval(
    address indexed owner,
    address indexed delegate,
    uint256 approvalIndex,
    bool isApproved
);
```

***

#### getUserApprovalIndex()

**Description:** Retrieves a user's current approval index from the Central Registry. This index is incremented when a user wants to revoke all delegations.

**Contract:** CentralRegistry

**Function signature:**

```solidity
function getUserApprovalIndex(address user) public view returns (uint256)
```

<table><thead><tr><th width="134.88336181640625">Type</th><th width="137.5">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>user</td><td>The user to check delegated approval index for.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="130.41668701171875">Type</th><th width="148.433349609375">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>user</td><td>The user to check delegated approval index for.</td></tr></tbody></table>

***

#### checkDelegationDisabled()

**Description:** Checks whether delegation is temporarily or permanently disabled for a specified user.

**Contract:** CentralRegistry

**Function signature:**

```solidity
function checkDelegationDisabled(address user) public view returns (bool)
```

<table><thead><tr><th width="131.5333251953125">Type</th><th width="151.89996337890625">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>user</td><td>The user to check delegation status for.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="143.08334350585938">Type</th><th>Description</th></tr></thead><tbody><tr><td>bool</td><td>Returns true if user has delegation disabled or if their cooldown period has not expired.</td></tr></tbody></table>

***

### Delegable Actions in ActionRegistry.sol

#### getUserApprovalIndex()

Description: Returns a user's approval index, which is used to authorize delegates across the entire protocol.

**Contract:** CentralRegistry

**Function signature:**

```solidity
function getUserApprovalIndex(address user) external view returns (uint256)
```

<table><thead><tr><th width="131.5333251953125">Type</th><th width="121.75">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>user</td><td>The user to check delegated approval index for.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="164.29998779296875">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>Current approval index for the specified user.</td></tr></tbody></table>

***

#### incrementApprovalIndex()

**Description**: Increments a caller's approval index, immediately revoking all delegate permissions across all Curvance contracts.

**Contract:** CentralRegistry

**Function signature:**

```solidity
function incrementApprovalIndex() external
```

**Events:**

```solidity
event ApprovalIndexIncremented(address indexed user, uint256 newIndex);
```

***

#### checkDelegationDisabled()

**Description:** Determines whether a user has delegation disabled, either intentionally or due to a cooldown period.

**Function signature:**

```solidity
function checkDelegationDisabled(address user) external view returns (bool)
```

<table><thead><tr><th width="123.7166748046875">Type</th><th width="130.2166748046875">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>user</td><td>The user to check delegation status for.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="121.86666870117188">Type</th><th>Description</th></tr></thead><tbody><tr><td>bool</td><td>Returns true if user has delegation disabled or if their cooldown period has not expired.</td></tr></tbody></table>

***

#### setDelegable()

**Description:** Sets a caller's status for whether to allow new delegations or not. When re-enabling delegation, the user's configured cooldown period applies.

Function signature:

```solidity
function setDelegable(bool delegationDisabled) external
```

**Parameters:**

<table><thead><tr><th width="113.66665649414062">Type</th><th width="199.98333740234375">Name</th><th>Description</th></tr></thead><tbody><tr><td>bool</td><td>delegationDisabled</td><td>true to disable delegation, false to enable delegation (after cooldown).</td></tr></tbody></table>

**Events:**

```solidity
event DelegableStatusChanged(
    address indexed user,
    bool delegable,
    uint256 delegationEnabledTimestamp
);
```


# Earn Vaults

### Overview

Earn Vaults are ERC4626-style vaults implemented by `LendingOptimizer.sol`. Each vault accepts one underlying ERC20 asset and allocates that asset across a configured list of Curvance `BorrowableCToken` markets that use the same underlying.

Users deposit the underlying asset into the vault and receive optimizer shares. The vault deposits the underlying into approved cToken markets as lending liquidity. Borrowers inside those isolated markets can use that liquidity, and interest earned by the cToken positions increases the vault's tracked assets and share price after accrual.

Earn Vaults do not borrow, post collateral, or choose a new target allocation on each user deposit. Deposits and exits route pro-rata across the vault's current market balances. Allocation percentages change through `rebalance()`, which can be called by an authorized harvester or market-permissioned address.

### Core Architecture

`LendingOptimizer` combines three main responsibilities:

* ERC4626-style accounting for deposits, mints, withdrawals, redemptions, share balances, previews, and share-to-asset conversion.
* Multi-market allocation across up to `MAX_MARKETS` approved `BorrowableCToken` markets for the same underlying asset.
* Authorized operations for market onboarding, cap updates, rebalancing, deposit pausing, fee updates, and idle-asset recovery.

After deployment, deposits are unavailable until `initializeDeposits()` is called. Initialization deposits a small base reserve into one approved market and mints dead shares to `address(0)` so the first live depositor cannot manipulate the initial exchange rate.

### Key Integrations

* `IERC20`: The underlying asset accepted by the vault. Users approve the optimizer before `deposit()` or `mint()`.
* `BorrowableCToken`: Each approved market receives the same underlying asset from the optimizer. The optimizer validates that each cToken has the matching underlying, is borrowable, has a registered Market Manager, and is listed in that Market Manager.
* `MarketManagerIsolated`: The optimizer reads cToken mint pause state through `actionsPaused(cToken)` and reads market-wide redemption pause state through `redeemPaused()`.
* `ICentralRegistry`: Permission checks come from the central registry. Market permissions govern setup and market management, harvester or market permissions govern rebalancing, DAO permissions govern `skim()`, and the DAO address receives performance fee shares.
* `OptimizerReader`: Offchain and frontend integrations can use this reader for market data, user data, estimated optimizer APY, and proposed rebalance actions plus allocation bounds.

### State Management

Important optimizer state:

| State                       | Unit                   | Meaning                                                                                                                        |
| --------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `approvedCTokensList`       | addresses              | Ordered list of cToken markets approved for allocation.                                                                        |
| `allocationCaps(cToken)`    | WAD                    | Maximum post-rebalance allocation for a market. Constructor and admin inputs use BPS, then the contract stores the cap as WAD. |
| `fee`                       | BPS                    | Performance fee charged on yield above the exchange-rate high watermark.                                                       |
| `exchangeRateHighWatermark` | WAD                    | Highest share price used for performance fee accounting.                                                                       |
| `mintPaused`                | enum-like `uint8`      | `0` means uninitialized, `1` means deposits active, and `2` means deposits paused.                                             |
| `_totalAssets`              | underlying asset units | Cached total assets tracked across approved markets.                                                                           |

Allocation caps are configured in BPS and stored in WAD. `10000` BPS equals `100%`, and `1e18` WAD equals `100%`. The sum of allocation caps must be at least `100%` when the optimizer is deployed, when a cap decrease would reduce total caps, and when a market is removed.

### Data Flow

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FH1mXoLeWphc0zkRHXflN%2FChatGPT%20Image%20May%205%2C%202026%2C%2005_17_07%20PM.png?alt=media&amp;token=e9e1c75d-98ca-4087-a270-bfebdbd418ad" alt=""><figcaption></figcaption></figure>

#### Deployment and Activation

1. The optimizer is deployed with one underlying asset, a central registry, initial approved cTokens, initial allocation caps in BPS, and a performance fee in BPS.
2. The constructor validates that the approved market list is non-empty, has at most `MAX_MARKETS` entries, has no duplicates, and has a matching cap for each market.
3. Each cToken must use the optimizer's underlying asset, be borrowable, point to a registered Market Manager, and be listed in that Market Manager.
4. The initial caps must each be greater than `0`, no more than `10000` BPS, and sum to at least `10000` BPS.
5. A market-permissioned address calls `initializeDeposits(targetMarket)`.
6. `initializeDeposits()` pulls the base underlying reserve from the caller, deposits it into the target market, mints dead shares to `address(0)`, and changes `mintPaused` from `0` to `1`.

#### Deposits and Mints

1. The user approves the optimizer to spend the underlying asset.
2. The user calls `deposit(assets, receiver)` or `mint(shares, receiver)`.
3. The optimizer rejects zero-sized deposits or mints, uninitialized vaults, paused vault deposits, and deposits when any approved market has minting paused.
4. The optimizer accrues all approved markets before routing the user action.
5. The optimizer pulls underlying from the caller.
6. The optimizer splits the deposit pro-rata across current market balances and deposits into the approved cToken markets.
7. The receiver gets optimizer shares.

`deposit()` mints shares from the recoverable value actually tracked after cToken deposit rounding. `mint()` computes the required assets for exact shares and uses a conversion roundtrip so the new position can cover the requested share amount.

#### Withdrawals and Redemptions

1. The user calls `withdraw(assets, receiver, owner)` or `redeem(shares, receiver, owner)`.
2. If the caller is not the `owner`, the optimizer spends optimizer-share allowance from `owner`.
3. The optimizer rejects zero-asset exits and rejects exits while any approved market has redemption paused.
4. The optimizer accrues all approved markets before routing the exit.
5. The optimizer withdraws pro-rata across approved markets, capped by each market's idle liquidity.
6. If total idle liquidity across markets cannot satisfy the requested exit, the transaction reverts.
7. The optimizer burns shares and transfers underlying assets to `receiver`.

`withdraw()` returns the shares burned for a target asset amount. `redeem()` returns the assets withdrawn for a target share amount. Both paths re-sync `_totalAssets` from the post-withdrawal cToken balances.

#### Rebalancing

1. An offchain keeper or bot computes one `ReallocationAction` per approved market, usually through `OptimizerReader.optimalRebalance()`.
2. An authorized harvester or market-permissioned address calls `rebalance(actions, bounds)`.
3. The optimizer accrues all markets and charges any performance fee before rebalancing.
4. The optimizer processes withdrawals first. Negative `assetsOrBps` values are withdrawals.
5. The optimizer processes deposits second. Positive `assetsOrBps` values are deposits.
6. Declared withdrawal assets must equal declared deposit assets.
7. The optimizer verifies that every post-rebalance market allocation is at or below its allocation cap and within the caller-specified `AllocationBound`.
8. The optimizer updates `_totalAssets` from the post-rebalance balances and emits `Rebalanced`.

The `actions` and `bounds` arrays must match the exact order and length of `approvedCTokensList`. Include zero-value actions for markets that do not need a move.

#### Market Removal

1. A market-permissioned address calls `removeApprovedAsset(cTokenToRemove, removeActions, bounds)`.
2. The optimizer requires more than one approved market and requires an explicit reallocation plan.
3. The market being removed must be approved and must not be paused for redemptions.
4. Remaining allocation caps must still sum to at least `100%`.
5. The optimizer redeems its full cToken position from the removed market.
6. The optimizer redistributes redeemed assets according to positive BPS values in `removeActions`; the values must sum to exactly `10000`.
7. Reallocation targets must be approved markets, cannot be the removed market, cannot be duplicated, and cannot have minting paused.
8. The approved market list uses swap-and-pop removal, so post-removal ordering can change. `bounds` must match the final list order.

#### Accrual and Fees

State-changing user and keeper flows call `_accrueIfNeeded()` before routing. Accrual calls `accrueIfNeeded()` on every approved cToken, refreshes `_totalAssets`, and charges performance fees only when the current exchange rate is above `exchangeRateHighWatermark`.

Performance fees are minted as optimizer shares to `centralRegistry.daoAddress()`. The fee is configured in BPS and capped by `MAX_FEE_BPS`, which is `5000`.

### Security and Risk Considerations

* Initialization is required. Before `initializeDeposits()`, `mintPaused` is `0`, `maxDeposit()` and `maxMint()` return `0`, and deposit paths revert.
* Deposits are globally blocked for the optimizer when any approved market has cToken minting paused.
* Withdrawals and redemptions are globally blocked for the optimizer when any approved market has redemptions paused.
* Withdrawals and redemptions depend on idle liquidity in the underlying cToken markets. A cached maximum value can be higher than what can be withdrawn in the current transaction if market liquidity is insufficient.
* Previews and conversions use cached `_totalAssets`. They are estimates, not settlement guarantees. State-changing entrypoints accrue markets before routing.
* `rebalance()` is the normal path for changing allocation percentages. User deposits and exits route against the current allocation.
* Allocation bounds protect rebalances against state changes between offchain computation and onchain execution.
* The optimizer validates post-rebalance allocations against configured caps.
* Performance fees are taken only on yield above the high watermark.
* Optimizer-specific state-changing entrypoints use `nonReentrant`.
* Idle underlying sitting directly on the optimizer is treated as excess and can be recovered by the DAO through `skim()`.

### Integration Considerations

* Treat each Earn Vault as a vault for one underlying asset. Do not send a different token to `deposit()` or `mint()`.
* Load vault, underlying, cToken, and reader addresses from the deployment registry for the chain you are on. Do not hardcode live addresses in reusable integrations.
* Check `asset()` before building a deposit form so the frontend uses the right ERC20 decimals and allowance target.
* Check `maxDeposit(receiver)` and `maxMint(receiver)` before showing deposit availability. Both return `0` when deposits are not available.
* Check `previewDeposit(assets)` or `previewMint(shares)` for estimates, but label them as estimates because state-changing paths accrue and route through cToken markets.
* Check `maxWithdraw(owner)` and `maxRedeem(owner)` before showing exit availability, then handle `LendingOptimizer__InsufficientLiquidity()` on execution.
* For fresh share price, use `exchangeRateUpdated()` or call `accrueIfNeeded()` before `exchangeRate()`. Both fresh paths update state.
* Use `OptimizerReader.getOptimizerMarketData()` for market-level views when a state update is acceptable. It calls `exchangeRateUpdated()` on each optimizer.
* Use `OptimizerReader.getOptimizerUserData()` when only user share balance and cached redeemable assets are needed.
* Use `OptimizerReader.optimalRebalance(optimizer, slippageBps)` to construct `rebalance()` calldata, then review the returned actions and bounds before submission.

### User Interaction Functions

#### Structs

Contract: `LendingOptimizer.sol`

```solidity
struct ReallocationAction {
    IBorrowableCToken cToken;
    int256 assetsOrBps;
}

struct AllocationBound {
    address cToken;
    uint256 minBps;
    uint256 maxBps;
}
```

`ReallocationAction.assetsOrBps` has two meanings:

* In `rebalance()`, positive values deposit assets and negative values withdraw assets.
* In `removeApprovedAsset()`, values are positive BPS percentages and must sum to `10000`.

`AllocationBound` values are BPS percentages and must match the current approved market order required by the function being called.

### Deposits

#### **deposit()**

**Description:** Deposits an exact amount of underlying assets and mints optimizer shares to `receiver`.

**Contract:** `LendingOptimizer.sol`

**Function signature:**

```solidity
function deposit(uint256 assets, address receiver) public returns (uint256 shares);
```

**Inputs:**

<table><thead><tr><th width="122">Type</th><th width="114">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>uint256</code></td><td><code>assets</code></td><td>Amount of underlying assets to deposit.</td></tr><tr><td><code>address</code></td><td><code>receiver</code></td><td>Account that receives optimizer shares.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="114">Type</th><th width="133">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>uint256</code></td><td><code>shares</code></td><td>Optimizer shares minted to <code>receiver</code>.</td></tr></tbody></table>

**Events:**

```solidity
Deposit(address indexed by, address indexed owner, uint256 assets, uint256 shares)
PerformanceFeeAccrued(uint256 feeShares, address indexed recipient) if accrual charges a performance fee
```

{% hint style="info" %}
Caller must approve the optimizer to spend at least `assets` of the underlying token. Reverts if `assets == 0`, the optimizer is uninitialized, deposits are paused, any approved market has minting paused, or the tracked deposit value mints zero shares. Deposits route pro-rata across current market balances.
{% endhint %}

#### **mint()**

**Description:** Mints an exact amount of optimizer shares by depositing the required amount of underlying assets.

**Contract:** `LendingOptimizer.sol`

**Function signature:**

```solidity
function mint(uint256 shares, address receiver) public returns (uint256 assets);
```

**Inputs:**

<table><thead><tr><th width="128">Type</th><th width="100">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>uint256</code></td><td><code>shares</code></td><td>Exact optimizer shares to mint.</td></tr><tr><td><code>address</code></td><td><code>receiver</code></td><td>Account that receives optimizer shares.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="119">Type</th><th width="102">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>uint256</code></td><td><code>assets</code></td><td>Underlying assets pulled from the caller.</td></tr></tbody></table>

**Events:**

```solidity
Deposit(address indexed by, address indexed owner, uint256 assets, uint256 shares)
PerformanceFeeAccrued(uint256 feeShares, address indexed recipient) if accrual charges a performance fee
```

{% hint style="info" %}
Caller must approve the optimizer to spend the required underlying assets. Reverts if `shares == 0`, the optimizer is uninitialized, deposits are paused, any approved market has minting paused, or cToken rounding makes the tracked assets insufficient for the requested shares.
{% endhint %}

### Withdrawals

#### **withdraw()**

**Description:** Withdraws an exact amount of underlying assets to `receiver` and burns the required optimizer shares from `owner`.

**Contract:** `LendingOptimizer.sol`

**Function signature:**

```solidity
function withdraw(uint256 assets, address receiver, address owner) public returns (uint256 shares);
```

**Inputs:**

<table><thead><tr><th width="124">Type</th><th width="116">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>uint256</code></td><td><code>assets</code></td><td>Exact underlying assets to withdraw.</td></tr><tr><td><code>address</code></td><td><code>receiver</code></td><td>Account that receives the underlying assets.</td></tr><tr><td><code>address</code></td><td><code>owner</code></td><td>Account whose optimizer shares are burned.</td></tr></tbody></table>

**Return data:**

| Type      | Name     | Description                           |
| --------- | -------- | ------------------------------------- |
| `uint256` | `shares` | Optimizer shares burned from `owner`. |

**Events:**

```solidity
Withdraw(address indexed by, address indexed to, address indexed owner, uint256 assets, uint256 shares)
// if accrual charges a performance fee
PerformanceFeeAccrued(uint256 feeShares, address indexed recipient)
hdraw(address indexed by, address indexed to, address indexed owner, uint256 assets, uint256 shares)
PerformanceFeeAccrued(uint256 feeShares, address indexed recipient) if accrual charges a performance fee
```

{% hint style="info" %}
If `msg.sender != owner`, the caller must have optimizer-share allowance from `owner`.

Reverts if `assets == 0`, any approved market has redemptions paused, or total available cToken liquidity cannot satisfy the withdrawal.
{% endhint %}

#### **redeem()**

**Description:** Burns an exact amount of optimizer shares from `owner` and sends the withdrawn underlying assets to `receiver`.

**Contract:** `LendingOptimizer.sol`

**Function signature:**

```solidity
function redeem(uint256 shares, address receiver, address owner) public returns (uint256 assets);
```

**Inputs:**

<table><thead><tr><th width="121">Type</th><th width="126">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>uint256</code></td><td><code>shares</code></td><td>Exact optimizer shares to burn.</td></tr><tr><td><code>address</code></td><td><code>receiver</code></td><td>Account that receives the underlying assets.</td></tr><tr><td><code>address</code></td><td><code>owner</code></td><td>Account whose optimizer shares are burned.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="106">Type</th><th width="104">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>uint256</code></td><td><code>assets</code></td><td>Underlying assets sent to <code>receiver</code>.</td></tr></tbody></table>

**Events:**

```solidity
Withdraw(address indexed by, address indexed to, address indexed owner, uint256 assets, uint256 shares)
ormanceFeeAccrued(uint256 feeShares, address indexed recipient) if accrual charges a performance fee
```

{% hint style="info" %}
If `msg.sender != owner`, the caller must have optimizer-share allowance from `owner`. Reverts if the redeemed shares convert to zero assets, any approved market has redemptions paused, or total available cToken liquidity cannot satisfy the redemption.
{% endhint %}

### Preview and Limit Views

**Contract:** `LendingOptimizer.sol`

| Function                                                                         | Description                                                                                                      |
| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `function previewDeposit(uint256 assets) public view returns (uint256 shares);`  | Returns the cached-share estimate for an asset deposit.                                                          |
| `function previewMint(uint256 shares) public view returns (uint256 assets);`     | Returns the cached-asset estimate for exact share minting.                                                       |
| `function previewWithdraw(uint256 assets) public view returns (uint256 shares);` | Returns the cached-share estimate for an asset withdrawal.                                                       |
| `function previewRedeem(uint256 shares) public view returns (uint256 assets);`   | Returns the cached-asset estimate for share redemption.                                                          |
| `function maxDeposit(address) public view returns (uint256);`                    | Returns `type(uint256).max` when deposits are active and no approved market has minting paused, otherwise `0`.   |
| `function maxMint(address) public view returns (uint256);`                       | Returns `type(uint256).max` when deposits are active and no approved market has minting paused, otherwise `0`.   |
| `function maxWithdraw(address owner) public view returns (uint256);`             | Returns `convertToAssets(balanceOf(owner))` unless any approved market has redemptions paused, then returns `0`. |
| `function maxRedeem(address owner) public view returns (uint256);`               | Returns `balanceOf(owner)` unless any approved market has redemptions paused, then returns `0`.                  |

{% hint style="info" %}
Preview and conversion functions read cached `_totalAssets`. `maxWithdraw()` and `maxRedeem()` do not fully model cToken idle liquidity. Execution can still revert if the approved markets cannot supply the exit.
{% endhint %}

#### Accounting and Market Views

**Contract:** `LendingOptimizer.sol`

| Function                                                                         | Description                                                                                       |
| -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `function asset() public view returns (address);`                                | Returns the underlying ERC20 asset.                                                               |
| `function name() public view returns (string memory);`                           | Returns the optimizer share token name.                                                           |
| `function symbol() public view returns (string memory);`                         | Returns the optimizer share token symbol.                                                         |
| `function balanceOf(address owner) public view returns (uint256);`               | Returns an account's optimizer share balance.                                                     |
| `function totalAssets() public view returns (uint256);`                          | Returns cached `_totalAssets`.                                                                    |
| `function convertToShares(uint256 assets) public view returns (uint256 shares);` | Converts assets to optimizer shares using cached accounting.                                      |
| `function convertToAssets(uint256 shares) public view returns (uint256 assets);` | Converts optimizer shares to assets using cached accounting.                                      |
| `function exchangeRate() public view returns (uint256);`                         | Returns cached share price in WAD. Returns WAD if supply is zero.                                 |
| `function exchangeRateUpdated() public returns (uint256);`                       | Accrues approved markets, charges fees if applicable, and returns the updated share price in WAD. |
| `function accrueIfNeeded() external;`                                            | Accrues approved markets and absorbs yield into optimizer accounting.                             |
| `function numApprovedMarkets() external view returns (uint256);`                 | Returns the approved market count.                                                                |
| `function getApprovedMarkets() external view returns (address[] memory);`        | Returns the approved market list.                                                                 |
| `function approvedCTokensList(uint256 index) external view returns (address);`   | Returns the approved market at `index`.                                                           |
| `function allocationCaps(address cToken) external view returns (uint256);`       | Returns the market allocation cap in WAD.                                                         |
| `function fee() external view returns (uint256);`                                | Returns the performance fee in BPS.                                                               |
| `function exchangeRateHighWatermark() external view returns (uint256);`          | Returns the fee high watermark in WAD.                                                            |
| `function mintPaused() external view returns (uint8);`                           | Returns `0`, `1`, or `2` for uninitialized, active, or paused deposit state.                      |
| `function MAX_FEE_BPS() external view returns (uint256);`                        | Returns the maximum allowed performance fee in BPS.                                               |
| `function MAX_MARKETS() external view returns (uint256);`                        | Returns the maximum number of approved markets.                                                   |
| `function centralRegistry() external view returns (ICentralRegistry);`           | Returns the central registry used by the optimizer.                                               |

**Events:**

```solidity
// from exchangeRateUpdated()
// or accrueIfNeeded() if accrual charges a fee
PerformanceFeeAccrued(uint256 feeShares, address indexed recipient) 
```

{% hint style="info" %}
`exchangeRateUpdated()` and `accrueIfNeeded()` are state-changing. Use `exchangeRate()` when a cached view is acceptable.
{% endhint %}


# Protocol Reader

## Overview

ProtocolReader is a read-only auxiliary contract for querying market, account, pricing, liquidation, leverage, and balance data from Curvance. It lets frontends, SDKs, data indexers, and external systems batch common reads into fewer calls.

ProtocolReader stores only immutable configuration through `centralRegistry` and exposes view-only public functions. It is not a protocol write path and does not modify market state.

## Key Features

* **Data Consolidation:** Consolidates multiple variable fetches into single view functions, significantly reducing the number of EVM calls needed for comprehensive protocol queries.
* **Pure View Interface:** Contains no active storage values beyond immutable configuration and consists entirely of view functions, making it seamlessly upgradeable to support new data formats or query requirements.
* **Comprehensive Data Access**: Provides three main categories of data:
  * **Static Market Data:** Token configurations, collateral requirements, liquidation parameters.
  * **Dynamic Market Data:** returned from view calls. Oracle prices, IRM rates, utilization, liquidity, and balances are current reads, but cToken exchange rates and snapshots use non-mutating view paths such as `exchangeRate()` and `getSnapshot()`. They do not accrue state the way write paths or `exchangeRateUpdated()` / `getSnapshotUpdated()` do. Helpers with `bufferTime` can project debt forward through `debtBalanceAtTimestamp(...)`.
  * **User Data:** Individual positions, collateral balances, debt positions, and veCVE locks.

## Data Structures

#### StaticMarketData

```solidity
struct StaticMarketData {
    address _address; // MarketManager address for this market.
    uint256[] adapters; // Unique oracle adaptor type IDs used by tokens in this market (0 means empty/unsupported slot).
    uint256 cooldownLength; // Market action cooldown length in seconds (see `MARKET_COOLDOWN_LENGTH`).
    StaticMarketToken[] tokens; // Static configuration for each listed cToken in this market.
}
```

#### StaticMarketToken

```solidity
struct StaticMarketToken {
    address _address; // cToken address.
    string name; // cToken name.
    string symbol; // cToken symbol.
    uint8 decimals; // cToken decimals, matching the underlying token decimals.
    StaticMarketAsset asset; // Underlying ERC20 metadata for this cToken.
    uint256 collateralCap; // Market-wide collateral cap for this cToken, in cToken shares.
    uint256 debtCap; // Market-wide debt cap for this cToken, in underlying assets.
    bool isListed; // Whether this cToken is listed in the market.
    bool mintPaused; // Whether minting/depositing is paused for this cToken.
    bool collateralizationPaused; // Whether enabling collateral is paused for this cToken.
    bool borrowPaused; // Whether borrowing is paused for this cToken.
    bool isBorrowable; // Whether the cToken contract reports `isBorrowable()`.
    uint256 collRatio; // Collateral borrow ratio, in BPS.
    uint256 maxLeverage; // Maximum leverage derived from `collRatio`, in BPS.
    uint256 collReqSoft; // Soft liquidation collateral requirement, in BPS.
    uint256 collReqHard; // Hard liquidation collateral requirement, in BPS.
    uint256 liqIncBase; // Base liquidation incentive, in BPS.
    uint256 liqIncCurve; // Liquidation incentive curve length from soft to hard liquidation, in BPS.
    uint256 liqIncMin; // Minimum liquidation incentive during auction, in BPS.
    uint256 liqIncMax; // Maximum liquidation incentive during auction, in BPS.
    uint256 closeFactorBase; // Base close factor for soft liquidation, in BPS.
    uint256 closeFactorCurve; // Close factor curve length from soft to hard liquidation, in BPS.
    uint256 closeFactorMin; // Minimum close factor during auction, in BPS.
    uint256 closeFactorMax; // Maximum close factor during auction, in BPS.
    uint256[2] adapters; // Oracle adaptor type IDs used for pricing. `0` means an empty or unsupported slot.
    uint256 irmTargetRate; // Annualized target borrow rate derived from the token IRM.
    uint256 irmMaxRate; // Annualized max borrow rate derived from the token IRM.
    uint256 irmTargetUtilization; // IRM target utilization, in WAD.
    uint256 interestFee; // Borrow interest fee charged by the cToken, in BPS.
}
```

#### StaticMarketAsset

```solidity
struct StaticMarketAsset {
    address _address; // Underlying ERC20 address.
    string name; // Underlying token name.
    string symbol; // Underlying token symbol.
    uint8 decimals; // Underlying token decimals.
    uint256 totalSupply; // Underlying token totalSupply() (native decimals).
}
```

#### DynamicMarketData

```solidity
struct DynamicMarketData {
    address _address; // MarketManager address for this market.
    DynamicMarketToken[] tokens; // Dynamic state for each listed cToken in this market.
}
```

#### DynamicMarketToken

```solidity
struct DynamicMarketToken {
    address _address; // cToken address.
    uint256 totalSupply; // Total cToken shares outstanding.
    uint256 exchangeRate; // Share to underlying exchange rate, in WAD.
    uint256 totalAssets; // Total underlying assets reported by the cToken.
    uint256 collateral; // Total cToken shares posted as collateral in the market.
    uint256 debt; // Total outstanding market debt, in underlying assets. `0` if not borrowable.
    uint256 sharePrice; // Higher oracle price for the cToken share token, in USD WAD.
    uint256 assetPrice; // Higher oracle price for the underlying asset, in USD WAD.
    uint256 sharePriceLower; // Lower oracle price for the cToken share token, in USD WAD.
    uint256 assetPriceLower; // Lower oracle price for the underlying asset, in USD WAD.
    uint256 borrowRate; // Current borrow rate per second, in WAD. `0` if not borrowable.
    uint256 predictedBorrowRate; // Predicted borrow rate per second after IRM adjustment, in WAD. `0` if not borrowable.
    uint256 utilizationRate; // Utilization rate from `0` to `WAD`. `0` if not borrowable.
    uint256 supplyRate; // Current supply rate per second, in WAD. `0` if not borrowable.
    uint256 liquidity; // Underlying assets currently held in the borrowable pool. `0` if not borrowable.
}
```

#### UserData

```solidity
struct UserData {
    UserLock[] locks; // veCVE locks for `account` (empty if veCVE not configured).
    UserMarket[] markets; // Per-market position summary for `account` across all markets.
}
```

#### UserLock

```solidity
struct UserLock {
    uint256 lockIndex; // Index into the user's veCVE lock arrays.
    uint256 amount; // Locked amount (token units as returned by veCVE).
    uint256 unlockTime; // Lock unlock timestamp, in seconds since epoch.
}
```

#### UserMarket

```solidity
struct UserMarket {
    address _address; // MarketManager address for this market.
    uint256 collateral; // Total collateral value across positions, in USD WAD (1e18).
    uint256 maxDebt; // Max borrowable debt value based on current collateral, in USD WAD (1e18).
    uint256 debt; // Total outstanding debt value across positions, in USD WAD (1e18).
    uint256 positionHealth; // Position health ratio (collateral requirement coverage), in WAD (1e18).
    uint256 cooldown; // Account cooldown end timestamp, in seconds since epoch.
    bool errorCodeHit; // Whether an oracle error was hit while computing values.
    UserMarketToken[] tokens; // Per-token balances/collateral/debt data for this market.
}
```

#### UserMarketToken

```solidity
struct UserMarketToken {
    address _address; // cToken address.
    uint256 userAssetBalance; // Underlying assets represented by `userShareBalance` through `convertToAssets`.
    uint256 userShareBalance; // User's cToken wallet balance, in shares.
    uint256 userUnderlyingBalance; // User's underlying ERC20 wallet balance.
    uint256 userCollateral; // User's collateral posted in this cToken, in cToken shares.
    uint256 userDebt; // User's outstanding debt for this cToken, in underlying assets. `0` if not borrowable.
    uint256 liquidationPrice; // Approx liquidation trigger price, in USD WAD. May be `type(uint256).max` if not applicable.
}
```

#### HypotheticalResult

```solidity
struct HypotheticalResult {
    uint256 collateral; // Total value of `account`'s collateral across all positions.
    uint256 maxDebt; // The maximum amount of debt `account` could take on based on `collateral`.
    uint256 debt; // Total value of `account`'s current outstanding debt across all positions.
    uint256 collateralSurplus; // Excess collateral when adjusted for debt obligations.
    uint256 liquidityDeficit; // Liquidity deficit when adjusted for debt obligations.
    bool loanSizeError; // Whether the desired loan size is insufficient causing an error.
    bool oracleError; // Whether an oracle error was hit when pricing assets.
}
```

#### UserMarketSummary

```solidity
struct UserMarketSummary {
    address _address; // MarketManager address for this market.
    uint256 collateral; // Total collateral value for `account`, in USD WAD.
    uint256 maxDebt; // Maximum debt value supported by current collateral, in USD WAD.
    uint256 debt; // Total outstanding debt value for `account`, in USD WAD.
    uint256 positionHealth; // Pessimistic position health ratio, in WAD. `type(uint256).max` when debt is zero.
    uint256 cooldown; // Cooldown expiry timestamp. Compare to `block.timestamp`.
    bool errorCodeHit; // Whether an oracle error was hit while computing values.
}
```

***

## User Interaction Functions

### Data Aggregation

#### **getAllDynamicState()**

**Description:** Returns both real-time market data across all markets and comprehensive user position data in a single call. It combines `getDynamicMarketData()` (current prices, interest rates, liquidity) with `getUserData()` (user's positions,&#x20;collateral, debt, veCVE locks) to minimize RPC calls for frontend dashboards.

**Function signature:**

```solidity
function getAllDynamicState(
    address account
) external view returns (
    DynamicMarketData[] memory market,
UserData memory user
 );
```

<table><thead><tr><th width="108">Type</th><th width="102">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>account</td><td>The address of the account to get market data for.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="214">Type</th><th>Description</th></tr></thead><tbody><tr><td><a href="#dynamicmarketdata">DynamicMarketData[]</a></td><td>Real-time market information including current prices, interest rates, liquidity, and utilization for all tokens across all markets.</td></tr><tr><td><a href="#userdata">UserData</a></td><td>The user's veCVE locks and position data across all markets including collateral, debt, health, and token balances.</td></tr></tbody></table>

***

#### getStaticMarketDat&#x61;**()**

**Description:** Returns immutable configuration data for all markets including token details, collateral requirements, debt caps, liquidation parameters, and oracle adapter types.

**Function signature:**

```solidity
function getStaticMarketData() public view returns (StaticMarketData[] memory data);
```

Return data:

<table><thead><tr><th width="185">Type</th><th>Description</th></tr></thead><tbody><tr><td><a href="#staticmarketdata">StaticMarketData[]</a></td><td>The address of the MarketManager, unique oracle adaptors, the market's cooldown length and tokens involved in the market.</td></tr></tbody></table>

***

#### getDynamicMarketDat&#x61;**()**

**Description:** Returns real-time data for all markets

**Function signature:**

```solidity
function getDynamicMarketData() external view returns (DynamicMarketData[] memory data)
```

**Return data:**

<table><thead><tr><th width="197">Type</th><th>Description</th></tr></thead><tbody><tr><td><a href="#dynamicmarketdata">DynamicMarketData[]</a></td><td>Real-time market information including current prices, interest rates, liquidity, and utilization for all tokens across all markets.</td></tr></tbody></table>

***

#### getUserDat&#x61;**()**

**Description:** Returns the current outstanding borrows across all Curvance markets.

**Function signature:**

```solidity
function getUserData(address account) public view returns (UserData memory data)
```

**Return data:**

<table><thead><tr><th width="125">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The current outstanding borrows inside Curvance, in WAD (18 decimals)</td></tr></tbody></table>

#### getBalancesOf()

**Description:** returns wallet balances for each token in order. If a token address is Curvance's native-token sentinel, it returns `account.balance`; otherwise it returns the ERC20 balance.

**Input data:**

<table><thead><tr><th width="142">Type</th><th width="112">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>address[]</code></td><td><code>tokens</code></td><td>Token addresses to query. The returned balances use the same order as this array.</td></tr><tr><td><code>address</code></td><td><code>account</code></td><td>Account whose wallet balances should be returned.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="109">Type</th><th width="107">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>uint256[]</code></td><td><code>balances</code></td><td>Wallet balances for <code>account</code>, one per input token. If <code>tokens[i]</code> is Curvance's native-token sentinel, <code>balances[i]</code> is <code>account.balance</code>; otherwise it is the ERC20 <code>balanceOf(account)</code> for <code>tokens[i]</code>.</td></tr></tbody></table>

### Price Oracle

#### **getPrice()**

**Description:** Returns `(price, errorCode)` from the Oracle Manager. When `inUSD` is true, `price` is USD-denominated WAD. When `inUSD` is false, `price` is quoted against the native token in WAD terms. \
Error code `0` means no oracle error. Error code `1` means the price should be treated with caution. Error code `2` means `BAD_SOURCE`; ProtocolReader returns `price = 0` for this case.

**Function signature:**

```solidity
function getPrice(
    address asset, 
    bool inUSD, 
    bool getLower) 
    public view returns(
    uint256 price, 
    uint256 errorCode
);
```

<table><thead><tr><th width="123">Type</th><th width="112">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>asset</td><td>Asset address to get prices for</td></tr><tr><td>bool</td><td>inUSD</td><td>Boolean whether to return prices in USD or the native token</td></tr><tr><td>bool</td><td>getLower</td><td>Boolean indicating whether to get the lower or higher price</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="124">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>Asset price in WAD ($1 = 1e18)</td></tr><tr><td>uint256</td><td>The reported error code, 0 if there is no error, 1 if there's a moderate oracle issue that blocks new borrowing, or 2 if there's a severe oracle issue that pauses all actions involving that asset.</td></tr></tbody></table>

***

### Position Health & Risk Assessment

#### **getPositionHealth()**

**Description:** Gets the Position health of `account` inside a market.

Function signature:

```solidity
function getPositionHealth(
    IMarketManager mm,
    address account,
    address cToken,
    address borrowableCToken,
    bool isDeposit,
    uint256 collateralAssets,
    bool isRepayment,
    uint256 debtAssets, 
    uint256 bufferTime
) public view returns (uint256 positionHealth, bool errorCodeHit) {
```

<table><thead><tr><th width="165">Type</th><th width="168">Name</th><th>Description</th></tr></thead><tbody><tr><td>IMarketManager</td><td>mm</td><td>The MarketManager to pull data from.</td></tr><tr><td>address</td><td>account</td><td>The user address to evaluate inside <code>mm</code></td></tr><tr><td>address</td><td>cToken</td><td>Optional collateral token for a hypothetical collateral-side action. Pass <code>address(0)</code> when not simulating collateral changes.</td></tr><tr><td>address</td><td>borrowableCToken</td><td>Optional debt token for a hypothetical debt-side action. Pass <code>address(0)</code> when not simulating debt changes.</td></tr><tr><td>bool</td><td>isDeposit</td><td>Whether <code>collateralAssets</code> should be added as a deposit or removed as a redemption.</td></tr><tr><td>uint256</td><td>collateralAssets</td><td>The amount of assets for a hypothetical action with <code>cToken</code>.</td></tr><tr><td>bool</td><td>isRepayment</td><td>Whether <code>debtAssets</code> should be treated as repayment or new borrow. If <code>debtAssets == type(uint256).max</code>, the function uses <code>debtBalanceAtTimestamp(account, borrowableCToken, block.timestamp + bufferTime)</code>.</td></tr><tr><td>uint256</td><td>debtAssets</td><td>The amount of assets for a hypothetical action with <code>borrowableCToken</code></td></tr><tr><td>uint256</td><td>bufferTime</td><td>Any additional time buffer debt accrual is expected before a user's action, in seconds.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="117">Type</th><th width="148"></th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>positionHealth</td><td>The healthiness of <code>account</code>'s position inside the market. <code>(positionHealth, errorCodeHit)</code>. <code>positionHealth</code> is WAD-scaled and <code>type(uint256).max</code> when debt is zero.</td></tr><tr><td>bool</td><td>errorCodeHit</td><td>Whether an error code was hit or not, which would provide incorrect Position Health</td></tr></tbody></table>

***

#### **hypotheticalRedemptionOf()**

**Description:** Determine what the account liquidity would be if&#x20;the given shares were redeemed.

Function signature:

```solidity
function hypotheticalRedemptionOf(
    address account,
    address cTokenModified,
    uint256 redemptionShares,
    uint256 bufferTime
) external view returns (uint256, uint256, bool, bool);
```

<table><thead><tr><th width="108">Type</th><th width="168">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>account</td><td>The account to determine liquidity for.</td></tr><tr><td>address</td><td>cTokenModified</td><td>The address of the market token</td></tr><tr><td>uint256</td><td>redemptionShares</td><td>The number of shares to hypothetically redeem.</td></tr><tr><td>uint256</td><td>bufferTime</td><td>Any additional time buffer debt accrual is expected before a user's action, in seconds.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="117">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>Hypothetical account liquidity in excess of collateral requirements.</td></tr><tr><td>uint256</td><td>Hypothetical account liquidity deficit below collateralrequirements.</td></tr><tr><td>bool</td><td>The amount of collateral posted or debt owed by the account.</td></tr><tr><td>bool </td><td>Whether an error code was hit.</td></tr></tbody></table>

***

#### maxRedemptionO&#x66;**()**

**Description:** Determine the maximum amount of `cTokenRedeemed` that `account` can redeem.

**Function signature:**

```solidity
function maxRedemptionOf(
    address account,
    address cTokenRedeemed,
    uint256 bufferTime
) public view returns (
    uint256 collateralizedSharesRedeemable,
    uint256 uncollateralizedShares,
    bool errorHit
);
```

<table><thead><tr><th width="113">Type</th><th width="198">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>account</td><td>The account to determine redemptions for.</td></tr><tr><td>address</td><td>cTokenRedeemed</td><td>The cToken to redeem.</td></tr><tr><td>uint256</td><td>bufferTime</td><td>Any additional time buffer debt accrual is expected before a user's action, in seconds.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="117">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The amount of collateralized <code>cTokenModified</code> shares redeemable by <code>account</code>.</td></tr><tr><td>uint256</td><td>The amount of uncollateralized <code>cTokenModified</code> shares redeemable by <code>account</code>.</td></tr><tr><td>bool</td><td>Whether an error code was hit.</td></tr></tbody></table>

***

#### **liquidationValuesOf()**

**Description:** Evaluates collateral and debt positions to determine account health and liquidation parameters.

Function signature:

```solidity
function liquidationValuesOf(
    IMarketManager mm,
    address account,
    bool isAuction
) public view returns (
    uint256 cSoft, uint256 cHard, uint256 debt, uint256 lFactor, bool) {
```

<table><thead><tr><th width="159">Type</th><th width="168">Name</th><th>Description</th></tr></thead><tbody><tr><td>IMarketManager</td><td>mm</td><td>The market manager to pull liquidation values from.</td></tr><tr><td>address</td><td>account</td><td>The address of the account being evaluated for liquidation.</td></tr><tr><td>bool</td><td>isAuction</td><td>Whether the liquidation is an auction or not, if true then applies <code>AUCTION_BUFFER</code> discount to cSoft/cHard indicating a closer/sooner liquidation value.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="117">Type</th><th width="134"></th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>cSoft</td><td>The account's soft collateral value (collateral adjusted by soft requirements).</td></tr><tr><td>uint256</td><td>cHard</td><td>The account's hard collateral value (collateral adjusted by hard requirements).</td></tr><tr><td>uint256</td><td>debt</td><td>The account's total debt value.</td></tr><tr><td>uint256 </td><td>lFactor</td><td>WAD-scaled: <code>0</code> means no liquidation, <code>WAD</code> means hard liquidation, and values between them represent soft-liquidation severity.</td></tr><tr><td>bool </td><td>errorCodeHit</td><td>Whether an error code was hit.</td></tr></tbody></table>

***

#### debtBalanceAtTimestam&#x70;**()**

**Description:** Returns the projected debt balance for `account` in `borrowableCToken` at `timestamp`. If the supplied timestamp is before the current block timestamp, the function uses the current block timestamp instead. This helper is intended for frontend data querying and sizing, not onchain execution.

**Function signature:**

```solidity
function debtBalanceAtTimestamp(
    address account,
    address borrowableCToken,
    uint256 timestamp
) public view returns (uint256 debtBalance);
```

<table><thead><tr><th width="109">Type</th><th width="174">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>account</td><td>The address whose debt balance should be calculated.</td></tr><tr><td>address</td><td>borrowableCToken</td><td>The token that <code>account</code>'s debt balance will be checked for.</td></tr><tr><td>uint256</td><td>timestamp</td><td>The unix timestamp to calculate account debt balance with.</td></tr></tbody></table>

Return data:

<table><thead><tr><th width="111">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The debt balance of <code>account</code> at <code>timestamp</code>.</td></tr></tbody></table>

#### **getLiquidationPrice()**

**Description: E**stimates the price at which the account would reach liquidation for a collateral-side (`long = true`) or debt-side (`long = false`) exposure. It returns `type(uint256).max` when there is no usable exposure, the token is unlisted, debt is zero, or the amount rounds to zero. It returns `errorHit = true` when a severe oracle error is hit while computing the value.

Function signature:

```solidity
function getLiquidationPrice(
    address account,
    address cToken,
    bool long
) public view returns (uint256 price, bool errorHit) {
```

<table><thead><tr><th width="165">Type</th><th width="168">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>account</td><td>The account address to check.</td></tr><tr><td>address</td><td>cToken</td><td>The address of the cToken that is being used as collateral.</td></tr><tr><td>bool</td><td>long</td><td>If the position is long or short.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="117">Type</th><th width="148"></th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>price</td><td>The collateral price to liquidate.</td></tr><tr><td>uint256</td><td>errorCodeHit</td><td>Whether an oracle error code was hit or not.</td></tr></tbody></table>

### Hypothetical Simulation Functions

#### hypotheticalRedemptionO&#x66;**()**

**Description:** Determine what the account liquidity would be if&#x20;the given shares were redeemed.

**Function signature:**

```solidity
function hypotheticalRedemptionOf(
    address account,
    address cTokenModified,
    uint256 redemptionShares
) public view returns (uint256, uint256, bool, bool)
```

<table><thead><tr><th width="99">Type</th><th width="166">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>account</td><td>The account to determine liquidity for.</td></tr><tr><td>address</td><td>cTokenModified</td><td>The cToken to hypothetically redeem.</td></tr><tr><td>uint256</td><td>redemptionShares</td><td>The number of shares to hypothetically redeem.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="159">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>Hypothetical account liquidity in excess of collateral requirements.</td></tr><tr><td>uint256</td><td>Hypothetical account liquidity deficit below collateral requirements.</td></tr><tr><td>bool</td><td>Whether the proposed action is not possible. NOT whether it passes liquidity constraints or not.</td></tr><tr><td>bool</td><td>Whether an error code was hit or not.</td></tr></tbody></table>

***

#### hypotheticalBorrowO&#x66;**()**

**Description:** Determine what the account liquidity would be if&#x20;the given assets were borrowed.

**Function signature:**

```solidity
function hypotheticalBorrowOf(
    address account,
    address borrowableCTokenModified,
    uint256 borrowAssets,
    uint256 bufferTime
) external view returns (uint256, uint256, bool, bool, bool);
```

<table><thead><tr><th width="126">Type</th><th width="237">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>account</td><td>The account to determine liquidity for.</td></tr><tr><td>address</td><td>borrowableCTokenModified</td><td>The borrowableCToken to hypothetically borrow.</td></tr><tr><td>uint256</td><td>borrowAssets</td><td>The number of assets to hypothetically borrow.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="132">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>Hypothetical account liquidity in excess of collateral requirements.</td></tr><tr><td>uint256</td><td>Hypothetical account liquidity deficit below collateral requirements.</td></tr><tr><td>bool</td><td>Whether the proposed action is possible. NOT whether it passes liquidity constraints or not.</td></tr><tr><td>bool</td><td>Whether the desired loan size is insufficient causing an error.</td></tr><tr><td>bool</td><td>Whether an error code was hit or not.</td></tr></tbody></table>

***

#### hypotheticalLeverageO&#x66;**()**

**Description:** Estimates leverage capacity from current account state, a new collateral deposit, oracle prices, caps, available borrow liquidity, and projected debt. It can overestimate executable leverage when the actual route pays AMM fees or suffers slippage. It can revert if either token is not listed in the relevant market or if the collateral token has no collateral ratio.

**Function signature:**

```solidity
function hypotheticalLeverageOf(
    address account,
    address cToken,
    address borrowableCToken,
    uint256 assets,
    uint256 bufferTime
) public view returns (
    uint256 currentLeverage,
    uint256 adjustedMaxLeverage,
    uint256 maxLeverage,
    uint256 maxDebtBorrowable,
    bool loanSizeError,
    bool oracleError
    )
```

<table><thead><tr><th width="123">Type</th><th width="184">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>account</td><td>The account to query maximum borrow amount for.</td></tr><tr><td>address</td><td>cToken</td><td>The token that <code>account</code> will deposit to leverage against.</td></tr><tr><td>address</td><td>borrowableCToken</td><td>The token that <code>account</code> will borrow assets from to achieve leverage.</td></tr><tr><td>uint256</td><td>assets</td><td>The amount of <code>cToken</code> underlying that <code>account</code> will deposit to leverage against.</td></tr><tr><td>uint256</td><td>bufferTime</td><td>Any additional time buffer debt accrual is expected before a user's action, in seconds.</td></tr></tbody></table>

Return data:

<table><thead><tr><th width="164">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>Returns the current leverage multiplier of <code>account</code>, in <code>WAD</code>.</td></tr><tr><td>uint256</td><td>Returns the maximum leverage multiplier of <code>account</code> after a hypothetical deposit action, adjusted by liquidity constraints, in <code>WAD</code>.</td></tr><tr><td>uint256</td><td>Returns the maximum leverage multiplier of <code>account</code> after a hypothetical deposit action, in <code>WAD</code>.</td></tr><tr><td>uint256</td><td>Returns the maximum remaining borrow amount allowed from <code>borrowableCToken</code>, measured in underlying token amount, after the new hypothetical deposit.</td></tr><tr><td>bool</td><td>Whether the desired loan size is insufficient causing an error.</td></tr><tr><td>bool</td><td>Whether an oracle error was hit when pricing assets.</td></tr></tbody></table>

***

#### getLeverageSnapshot()

**Description:** Returns the account's projected aggregate collateral and debt values, the collateral underlying price, the collateral share price, the debt underlying price, the projected debt token balance, and an oracle-error flag. It is designed for leverage and deleverage sizing flows.

```solidity
function getLeverageSnapshot(
    address account,
    address cToken,
    address borrowableCToken,
    uint256 bufferTime
) external view returns (
    uint256 collateralUsd,
    uint256 debtUsd,
    uint256 collateralAssetPrice,
    uint256 sharePrice,
    uint256 debtAssetPrice,
    uint256 debtTokenBalance,
    bool oracleError
);
```

**Input data:**

<table><thead><tr><th width="118">Type</th><th width="172">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>address</code></td><td><code>account</code></td><td>The user address to snapshot.</td></tr><tr><td><code>address</code></td><td><code>cToken</code></td><td>The collateral cToken. ProtocolReader uses this token's MarketManager for the aggregate position snapshot and prices this token's underlying as the collateral asset.</td></tr><tr><td><code>address</code></td><td><code>borrowableCToken</code></td><td>The debt cToken used for debt-side price conversion and projected debt-token balance.</td></tr><tr><td><code>uint256</code></td><td><code>bufferTime</code></td><td>Seconds of debt accrual to project forward when computing aggregate debt and <code>debtTokenBalance</code>.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="113">Type</th><th width="217">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>uint256</code></td><td><code>collateralUsd</code></td><td>Aggregate collateral value for <code>account</code> in the <code>cToken</code> market, in USD WAD.</td></tr><tr><td><code>uint256</code></td><td><code>debtUsd</code></td><td>Aggregate projected debt value for <code>account</code> in the <code>cToken</code> market, in USD WAD.</td></tr><tr><td><code>uint256</code></td><td><code>collateralAssetPrice</code></td><td>Lower USD WAD price for the collateral cToken's underlying asset.</td></tr><tr><td><code>uint256</code></td><td><code>sharePrice</code></td><td>Collateral cToken share price, calculated as <code>collateralAssetPrice * exchangeRate(cToken) / WAD</code>.</td></tr><tr><td><code>uint256</code></td><td><code>debtAssetPrice</code></td><td>Higher USD WAD price for the debt cToken's underlying asset.</td></tr><tr><td><code>uint256</code></td><td><code>debtTokenBalance</code></td><td>Projected debt balance for <code>account</code> in <code>borrowableCToken</code>, in underlying token units.</td></tr><tr><td><code>bool</code></td><td><code>oracleError</code></td><td>Whether the aggregate liquidity snapshot hit an oracle error while pricing account positions.</td></tr></tbody></table>

***

#### hypotheticalLiquidityO&#x66;**()**

**Description:** Calculates the hypothetical maximum amount of `borrowableCToken` assets `account` can borrow for maximum leverage based on a new `cToken` collateralized deposit.

**Function signature:**

```solidity
function hypotheticalLiquidityOf(
    IMarketManager mm,
    address account,
    address cTokenModified,
    uint256 redemptionShares,
    uint256 borrowAssets,
    uint256 bufferTime
) public view returns (HypotheticalResult memory result) {
```

<table><thead><tr><th width="164">Type</th><th width="183">Name</th><th>Description</th></tr></thead><tbody><tr><td>IMarketManager</td><td>mm</td><td>The market manager to read account liquidity from.</td></tr><tr><td>address</td><td>account</td><td>The user address to evaluate.</td></tr><tr><td>address</td><td>cTokenModified</td><td>The cToken affected by the hypothetical action.</td></tr><tr><td>uint256</td><td>redemptionShares</td><td>The number of cToken shares hypothetically redeemed.</td></tr><tr><td>uint256</td><td>borrowAssets</td><td>The amount of underlying assets hypothetically borrowed.</td></tr><tr><td>uint256</td><td>bufferTime</td><td>The number of seconds of debt accrual to project before the action.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="189">Type</th><th width="194">Name</th><th>Description</th></tr></thead><tbody><tr><td><a href="#hypotheticalresult">HypotheticalResult</a></td><td>result</td><td>Hypothetical results for an action.</td></tr></tbody></table>

***

### Market Analysis Functions

#### marketMultiCooldow&#x6E;**()**

**Description:** Returns cooldown expiry timestamps for each market, not durations or remaining seconds. Each value is `accountAssets(user) + MARKET_COOLDOWN_LENGTH`. Compare the returned timestamp to the current block timestamp; if it is in the past, the account is not currently cooling down in that market.

**Function signature:**

```solidity
function marketMultiCooldown(
    address[] calldata markets,
    address user
) public view returns (uint256[] memory)
```

<table><thead><tr><th width="110">Type</th><th width="117">Name</th><th>Description</th></tr></thead><tbody><tr><td>address[]</td><td>markets</td><td>The list of market addresses.</td></tr><tr><td>address</td><td>user</td><td>The user address.</td></tr></tbody></table>

Return data:

<table><thead><tr><th width="119">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256[]</td><td>The list of cooldown periods for each market.</td></tr></tbody></table>

***

#### previewAssetImpac&#x74;**()**

**Description:** previews projected IRM rates after a potential deposit or borrow. It returns `(supply, borrow)`, where both values are per-second rates in WAD.

The `supply` value is populated only when `collateralCToken.isBorrowable()` is true. The `borrow` value is populated only when `user` already has debt in `debtBorrowableCToken`; otherwise it remains zero in the current implementation.

**Function signature:**

```solidity
function previewAssetImpact(
    address user,
    address collateralCToken,
    address debtBorrowableCToken,
    uint256 newCollateralAssets,
    uint256 newDebtAssets
) public view returns (uint256 supply, uint256 borrow);
```

<table><thead><tr><th width="112">Type</th><th width="213">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>user</td><td>The address of the user account to preview asset impact of.</td></tr><tr><td>address</td><td>collateralCToken</td><td>The address of the collateral cToken.</td></tr><tr><td>address</td><td>debtBorrowableCToken</td><td>The address of the debt borrowable cToken.</td></tr><tr><td>uint256</td><td>newCollateralAssets</td><td>The amount of new collateral asset to deposit, in assets.</td></tr><tr><td>uint256</td><td>newDebtAssets</td><td>The amount of new debt asset to borrow, in assets.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="115">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The projected supply rate, in <code>WAD</code> seconds.</td></tr><tr><td>uint256</td><td>The projected borrow amount, in <code>WAD</code> seconds.</td></tr></tbody></table>

***


# Protocol Managers

### Overview

`ProtocolManager` contracts are internal governance and operations helpers for changing privileged Curvance configuration through narrower authority surfaces than the full DAO or Emergency Council.

This family is not one inheritance tree. The base `ProtocolManager` implements scoped, period-limited management for market, oracle, IRM, pause, and position-manager actions. `ProtocolManagerDeployment` and `ProtocolManagerMassPause` are standalone managers for deployment-phase market setup and emergency pause operations.

### Manager Family

| Manager                     | Role                                                                                                                                                                   | Source                                                 |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| `ProtocolManager`           | General manager for bounded configuration updates on approved managed addresses.                                                                                       | `contracts/architecture/ProtocolManager.sol`           |
| `ProtocolManagerDeployment` | Atomic PMD market bootstrap helper that lists two cTokens, seeds cToken reserves, pauses minting, writes token configs, and records a one-time mint-unpause allowance. | `contracts/architecture/ProtocolManagerDeployment.sol` |
| `ProtocolManagerMassPause`  | Emergency helper that pauses or unpauses action groups across explicit markets or all registered markets.                                                              | `contracts/architecture/ProtocolManagerMassPause.sol`  |

### Shared Trust Model

All three managers store the Curvance `centralRegistry` and rely on permissions enforced by the contracts they call.

The base `ProtocolManager` has two authority layers:

1. `centralRegistry.hasElevatedPermissions(msg.sender)` gates `updateManagementConfig()`.
2. The immutable `protocolManager` address gates the operational update functions.

The standalone helpers use an immutable `owner`:

1. `ProtocolManagerDeployment.owner()` can call `deployMarket()` and `unpauseMarket()`.
2. `ProtocolManagerDeployment.revokeUnpause()` can be called by the owner or by an address with market permissions.
3. `ProtocolManagerMassPause.owner()` can call all mass pause and unpause functions.

Target contracts still enforce their own permissions. A deployed protocol manager must be granted the relevant `CentralRegistry` permissions, usually market permissions, before its downstream calls can succeed.

### Lifecycle Map

1. Governance deploys a manager with a `centralRegistry`, an authority address, and any manager-specific capability settings.
2. Governance grants the manager contract the permissions needed by downstream targets, such as market permissions for `MarketManagerIsolated`, `DynamicIRM`, oracle adaptor, or `CombinedAggregator` writes.
3. For the base `ProtocolManager`, elevated permissions configure the managed address set and period limits through `updateManagementConfig()`.
4. The configured authority address executes bounded updates through the manager instead of calling the target contracts directly.
5. For PMD market setup, `ProtocolManagerDeployment` performs the one-time market listing and mint-pause flow, then later consumes or revokes the pending mint-unpause allowance.
6. For emergency operations, `ProtocolManagerMassPause` applies action-specific pause scopes across a provided market list or every registered market.

### Permission Dependencies

| Target action                         | Manager contract                                                           | Downstream permission required by target      |
| ------------------------------------- | -------------------------------------------------------------------------- | --------------------------------------------- |
| Market token config                   | `ProtocolManager`, `ProtocolManagerDeployment`                             | Market permissions on `MarketManagerIsolated` |
| Market pause flags                    | `ProtocolManager`, `ProtocolManagerDeployment`, `ProtocolManagerMassPause` | Market permissions on `MarketManagerIsolated` |
| Position manager registry             | `ProtocolManager`                                                          | Market permissions on `MarketManagerIsolated` |
| Dynamic IRM config                    | `ProtocolManager`                                                          | Market permissions on `DynamicIRM`            |
| Oracle adaptor PriceGuard config      | `ProtocolManager`                                                          | Market permissions on `BaseOracleAdaptor`     |
| Combined aggregator PriceGuard config | `ProtocolManager`                                                          | Market permissions on `CombinedAggregator`    |

### Integration Considerations

* Treat these pages as internal operator documentation, not public user guidance.
* Read live manager addresses from the deployment registry for the chain being operated on.
* Do not conflate the base `ProtocolManager.protocolManager()` authority with the standalone helper `owner()` address.
* Before routing a transaction through a manager, confirm both layers: the caller is authorized on the manager, and the manager contract itself has the permission required by the target.
* When a helper can unpause, confirm whether the unpause scope clears only the intended pause flags or every flag in that action group.

### Related Pages

* ProtocolManager
* ProtocolManagerDeployment
* ProtocolManagerMassPause


# ProtocolManager

### Overview

`ProtocolManager` is a capability-gated configuration hub for designated Curvance protocol addresses. It lets a configured authority address modify approved market, IRM, oracle guard, pause, and position-manager settings while enforcing per-period adjustment limits where the manager supports bounded changes.

The contract is intended for internal governance operations. It does not own the target contracts by itself; the target contracts still check `CentralRegistry` permissions when `ProtocolManager` calls them.

### Core Architecture

`ProtocolManager` stores:

| Item                   | Getter                                                                  | Purpose                                                                           |
| ---------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| Central registry       | `centralRegistry()`                                                     | Permission hub used by this manager and downstream target contracts.              |
| Operational authority  | `protocolManager()`                                                     | Address allowed to call the bounded operational functions.                        |
| Capability flags       | `canModifyTokenConfig()`, `canModifyPriceGuards()`, and related getters | Immutable switches that decide which operation families this manager can perform. |
| Managed address config | `config(address)`                                                       | Per-address authority bit and `PeriodLimits`.                                     |
| Period adjustments     | `getMarketPeriodAdjustments()` and `getPriceGuardPeriodAdjustments()`   | Per-period accounting used to enforce bounded configuration movement.             |

Only elevated-permission addresses can call `updateManagementConfig()`. All other mutating operations require `msg.sender == protocolManager`, the matching capability flag, and authority over the managed address.

### Managed Configuration

The constructor accepts a `PermsConfig`, an initial list of managed addresses, and one `PeriodLimits` struct for each address.

```solidity
struct ManagementConfig {
    bool hasAuthority;
    PeriodLimits limits;
}

struct PermsConfig {
    bool canModifyPriceGuards;
    bool canDisablePriceGuards;
    bool canModifyTokenConfig;
    bool canModifyIRM;
    bool canUnpause;
    bool canModifyMintStatus;
    bool canModifyCollateralizationStatus;
    bool canModifyBorrowStatus;
    bool canModifyLiquidationStatus;
    bool canModifyRedeemStatus;
    bool canModifyTransferStatus;
    bool canModifyPositionManagers;
}
```

`updateManagementConfig()` can add or remove authority for addresses. When `hasAuthority` is false, the manager deletes that address's stored limits.

### Period Limit Model

`ProtocolManager` buckets adjustments into one-week periods:

| Constant                |        Value | Meaning                                                 |
| ----------------------- | -----------: | ------------------------------------------------------- |
| `PERIOD_DURATION`       |     `604800` | Period length in seconds.                               |
| `_UNIX_START_TIMESTAMP` | `1766966400` | Internal timestamp anchor used to derive period starts. |

For bounded changes, the manager computes:

```
new adjustment = new value - current value + existing adjustment for this period
```

The absolute value of that adjustment must be less than or equal to the relevant `PeriodLimits` field. If it exceeds the limit, the call reverts with `ProtocolManager__ParametersAreInvalid()`.

The adjustment key depends on the operation:

| Operation                         | Adjustment storage key      |
| --------------------------------- | --------------------------- |
| `updateTokenConfig()`             | `n.cToken`                  |
| `updateDynamicIRM()`              | `managedAddress` DynamicIRM |
| `setGuardedPriceConfig()`         | `asset`                     |
| `setGuardedPriceConfigCombined()` | `asset`                     |

### Period Limit Fields

```solidity
struct PeriodLimits {
    uint24 collRatioLimit;
    uint24 collReqSoftLimit;
    uint24 collReqHardLimit;
    uint120 collateralCapLimit;
    uint64 baseInterestRateLimit;
    uint112 debtCapLimit;
    uint64 vertexInterestRateLimit;
    uint64 vertexStartLimit;
    uint16 adjustmentVelocityLimit;
    uint16 decayPerAdjustmentLimit;
    uint24 vertexMultiplierMaxLimit;
    uint96 basePriceUSDLimit;
    uint96 minPriceUSDLimit;
    uint96 basePriceNativeLimit;
    uint96 minPriceNativeLimit;
}
```

The manager validates requested limits against hard maximums before storing them:

<table><thead><tr><th width="340">Constant</th><th align="right">Value</th></tr></thead><tbody><tr><td><code>MAXIMUM_COLL_RATIO_LIMIT</code></td><td align="right"><code>500</code></td></tr><tr><td><code>MAXIMUM_COLL_REQ_LIMIT</code></td><td align="right"><code>500</code></td></tr><tr><td><code>MAXIMUM_COLL_CAP_LIMIT</code></td><td align="right"><code>type(uint112).max</code></td></tr><tr><td><code>MAXIMUM_DEBT_CAP_LIMIT</code></td><td align="right"><code>type(uint104).max</code></td></tr><tr><td><code>MAXIMUM_INTEREST_RATE_LIMIT</code></td><td align="right"><code>1000</code></td></tr><tr><td><code>MAXIMUM_ADJUSTMENT_VELOCITY_LIMIT</code></td><td align="right"><code>300</code></td></tr><tr><td><code>MAXIMUM_DECAY_RATE_LIMIT</code></td><td align="right"><code>120</code></td></tr><tr><td><code>MAXIMUM_VERTEX_MULTIPLIER_MAX_LIMIT</code></td><td align="right"><code>50000</code></td></tr><tr><td><code>MAXIMUM_PRICE_GUARD_PRICE_LIMIT</code></td><td align="right"><code>type(uint88).max</code></td></tr></tbody></table>

`MINIMUM_LIQUIDATION_INCENTIVE` is `150`; `updateTokenConfig()` rejects any token config where `liqIncMin` is below that floor.

### Operation Groups

#### Market Token Config

`updateTokenConfig()` forwards a `MarketManagerIsolated.TokenConfig` to a managed market manager after checking:

* This manager can modify token configs.
* The managed market manager has authority.
* The cToken in the config has authority.
* The cToken is listed in the target market.
* `liqIncMin` is at least `MINIMUM_LIQUIDATION_INCENTIVE`.
* The period adjustments for collateral ratio, collateral requirements, collateral cap, and debt cap stay within the cToken's limits.

The `TokenConfig` field order is:

```solidity
struct TokenConfig {
    address cToken;
    uint256 collRatio;
    uint256 collReqSoft;
    uint256 collReqHard;
    uint256 liqIncBase;
    uint256 liqIncHard;
    uint256 liqIncMin;
    uint256 liqIncMax;
    uint256 closeFactorBase;
    uint256 closeFactorMin;
    uint256 closeFactorMax;
    uint256 collateralCap;
    uint256 debtCap;
}
```

#### Dynamic IRM Config

`updateDynamicIRM()` updates a managed `DynamicIRM`. Inputs use per-year and BPS values, while the target `DynamicIRM` stores per-second rate fields internally. Before forwarding the update, `ProtocolManager` converts the current per-second rates back to BPS for period-limit accounting.

The tracked fields are `baseRatePerYear`, `vertexRatePerYear`, `vertexStart`, `adjustmentVelocity`, `decayPerAdjustment`, and `vertexMultiplierMax`.

#### Price Guards

`setGuardedPriceConfig()` changes a `BaseOracleAdaptor` guard for a specific `(asset, inUSD)` route. The manager requires:

* Authority over the adaptor address.
* Authority over the asset.
* `canModifyPriceGuards == true`.
* The adaptor supports the asset.
* `timestampStart` and `ips` match the current guard values.
* `basePrice` and `minPrice` movements stay within the asset's period limits.

`disableGuardedPriceConfig()` removes an adaptor guard for `(asset, inUSD)`. It uses `canDisablePriceGuards`, not `canModifyPriceGuards`, and does not apply period limits.

#### Combined Aggregator Guards

`setGuardedPriceConfigCombined()` and `disableGuardedPriceConfigCombined()` operate on `CombinedAggregator`, which has one global `pg()` guard instead of per-asset guard storage.

Before mutating the aggregator, `_checkCombinedAggregatorAuthority()` confirms:

1. The caller has normal authority over the aggregator and asset.
2. `centralRegistry.oracleManager()` returns the oracle manager.
3. `IOracleManager.getPricingAdaptors(asset)[0]` returns a chainlink-style adaptor.
4. `IChainlinkStyleAdaptor.assetConfig(asset, inUSD)` points to `managedAddress` as the configured aggregator.

This route check prevents a manager from changing a combined aggregator guard without proving that the aggregator is the active route for the authorized asset and denomination.

#### Pause Flags

`setPaused()` maps `action` values to market pause setters:

<table><thead><tr><th width="92" align="right">Action</th><th width="254">Scope</th><th>Target call</th></tr></thead><tbody><tr><td align="right"><code>0</code></td><td>Market-wide liquidation</td><td><code>setLiquidationPaused(state)</code></td></tr><tr><td align="right"><code>1</code></td><td>Market-wide redeem</td><td><code>setRedeemPaused(state)</code></td></tr><tr><td align="right"><code>2</code></td><td>Market-wide transfer</td><td><code>setTransferPaused(state)</code></td></tr><tr><td align="right"><code>3</code></td><td>Token-level mint</td><td><code>setMintPaused(cToken, state)</code></td></tr><tr><td align="right"><code>4</code></td><td>Token-level collateralization</td><td><code>setCollateralizationPaused(cToken, state)</code></td></tr><tr><td align="right"><code>5</code></td><td>Token-level borrow</td><td><code>setBorrowPaused(cToken, state)</code></td></tr></tbody></table>

For token-level actions, the cToken must also have authority. If `state` is false, `canUnpause` must be true or the call reverts.

#### Position Managers

`updatePositionManager()` adds or removes a position manager on a managed market manager. The operation requires `canModifyPositionManagers` and authority over the market manager.

### Events and Errors

`ProtocolManager` emits `ManagementAuthorityUpdated(address addressManaged, bool manages, PeriodLimits limits)` whenever management configuration changes.

The contract defines these custom errors:

| Error                                     | Meaning                                                                        |
| ----------------------------------------- | ------------------------------------------------------------------------------ |
| `ProtocolManager__ParametersAreInvalid()` | Input validation, limit validation, asset support, or action ID checks failed. |
| `ProtocolManager__Unauthorized()`         | Caller, capability flag, managed address, or asset authority check failed.     |
| `ProtocolManager__MulDivFailed()`         | Internal multiplication/division helper failed.                                |
| `ProtocolManager__UintToIntError()`       | Internal signed conversion would overflow.                                     |

### Integration Considerations

* Confirm the manager contract itself has market permissions before expecting downstream writes to succeed.
* Configure both the target address and any relevant asset address in `config(address)` before using asset-scoped functions.
* Use the current `getPeriodTimestamp()` value when querying period adjustments for the active period.
* Query token config adjustments by cToken address, not by market manager address.
* Query PriceGuard adjustments by asset address, not by adaptor or aggregator address.
* Do not use `disableGuardedPriceConfig*()` as a bounded PriceGuard update. Disabling guards is a separate capability that bypasses period-limit accounting.
* A manager with `canUnpause == false` can still pause any pause scope it is authorized for, but it cannot unpause through `setPaused()`.

### Callable Functions

#### Constructor

```solidity
constructor(
    ICentralRegistry cr,
    address pm,
    ProtocolManager.PermsConfig memory p,
    address[] memory managedAddresses,
    ProtocolManager.PeriodLimits[] memory l
);
```

Deploys the manager, validates the central registry, stores the operational authority, configures the initial managed addresses, and stores immutable capability flags.

#### Configuration Reads

```solidity
function config(address managedAddress)
    external
    view
    returns (bool hasAuthority, ProtocolManager.PeriodLimits memory limits);

function getMarketPeriodAdjustments(
    address managedAddress,
    uint256 periodTimestamp
) external view returns (
    int24 collRatio,
    int24 collReqSoft,
    int24 collReqHard,
    int120 collateralCap,
    int112 debtCap,
    int64 baseInterestRate,
    int64 vertexInterestRate,
    int64 vertexStart,
    int16 adjustmentVelocity,
    int16 decayPerAdjustment,
    int24 vertexMultiplierMax
);

function getPriceGuardPeriodAdjustments(
    address managedAddress,
    uint256 periodTimestamp
) external view returns (
    int96 basePriceUSD,
    int96 minPriceUSD,
    int96 basePriceNative,
    int96 minPriceNative
);

function getPeriodTimestamp() public view returns (uint256 x);
```

Use these reads to inspect the configured managed set, current period bucket, and tracked adjustments. Compiler-generated getters also expose the immutable capability flags, constants, `centralRegistry()`, and `protocolManager()`.

#### Management Configuration

```solidity
function updateManagementConfig(
    address[] memory managedAddresses,
    ProtocolManager.PeriodLimits[] memory l,
    bool hasAuthority
) external;
```

Adds or removes managed-address authority. Only an address with elevated permissions in the central registry can call this function.

#### Token Config

```solidity
function updateTokenConfig(
    address managedAddress,
    MarketManagerIsolated.TokenConfig memory n
) external;
```

Applies a token config to a managed `MarketManagerIsolated`. Requires token-config capability, authority over the market and cToken, and period-limit compliance for the tracked fields.

#### Dynamic IRM

```solidity
function updateDynamicIRM(
    address managedAddress,
    uint256 baseRatePerYear,
    uint256 vertexRatePerYear,
    uint256 vertexStart,
    uint256 adjustmentVelocity,
    uint256 decayPerAdjustment,
    uint256 vertexMultiplierMax,
    bool vertexReset
) external;
```

Updates a managed `DynamicIRM` after applying period-limit accounting to the IRM parameters.

#### Price Guards

```solidity
function setGuardedPriceConfig(
    address managedAddress,
    address asset,
    bool inUSD,
    uint256 timestampStart,
    uint256 ips,
    uint256 basePrice,
    uint256 minPrice
) external;

function setGuardedPriceConfigCombined(
    address managedAddress,
    address asset,
    bool inUSD,
    uint256 timestampStart,
    uint256 ips,
    uint256 basePrice,
    uint256 minPrice
) external;

function disableGuardedPriceConfig(
    address managedAddress,
    address asset,
    bool inUSD
) external;

function disableGuardedPriceConfigCombined(
    address managedAddress,
    address asset,
    bool inUSD
) external;
```

The `set*` functions enforce PriceGuard period limits. The `disable*` functions use the separate disable capability and bypass period-limit accounting.

#### Pause Flags

```solidity
function setPaused(
    address managedAddress,
    address cToken,
    uint8 action,
    bool state
) external;
```

Sets one market-wide or token-level pause flag. Pass `address(0)` for `cToken` when `action` is `0`, `1`, or `2`.

#### Position Managers

```solidity
function updatePositionManager(
    address managedAddress,
    address pm,
    bool add
) external;
```

Adds or removes `pm` as a position manager on a managed `MarketManagerIsolated`.


# ProtocolManagerDeployment

### Overview

`ProtocolManagerDeployment` is a standalone deployment-phase manager for PMD market setup. It atomically lists a two-token isolated market, seeds the cToken dead-share reserves, pauses minting on both tokens, writes both token configs, and records a one-time mint-unpause allowance for that market.

The contract does not inherit `ProtocolManager` because it does not need period-limit tracking or a managed-address registry. Its surface is intentionally small: deploy a market, consume the one-time mint unpause, or revoke that unpause allowance.

### Core Architecture

<table><thead><tr><th width="201">Item</th><th width="250">Getter</th><th>Purpose</th></tr></thead><tbody><tr><td>Central registry</td><td><code>centralRegistry()</code></td><td>Used to validate market-manager registration and market-permission callers.</td></tr><tr><td>Deployment owner</td><td><code>owner()</code></td><td>Address authorized to call <code>deployMarket()</code> and <code>unpauseMarket()</code>.</td></tr><tr><td>Reserve amount</td><td><code>BASE_UNDERLYING_RESERVE()</code></td><td><code>77777</code> raw units of each underlying asset, matching the cToken initialization reserve.</td></tr><tr><td>Pending unpause state</td><td><code>pendingUnpause(address)</code></td><td>One-time mint-unpause allowance keyed by market manager.</td></tr></tbody></table>

The deployed `ProtocolManagerDeployment` contract must have market permissions in `CentralRegistry` before it can call `listTokens()`, `setMintPaused()`, or `updateTokenConfig()` on `MarketManagerIsolated`.

### Deployment Flow

`deployMarket()` performs this sequence:

1. Checks that `msg.sender == owner`.
2. Checks that `config0.cToken == token0` and `config1.cToken == token1`.
3. Checks that `centralRegistry.isMarketManager(marketManager)` is true.
4. Reads each cToken's underlying asset through `ICToken.asset()`.
5. Rejects the call if either underlying address has no code.
6. Pulls `77777` raw units of each underlying from the caller.
7. Approves each cToken to spend its corresponding reserve amount.
8. Calls `MarketManagerIsolated.listTokens(token0, token1)`.
9. Pauses minting on both listed tokens.
10. Calls `updateTokenConfig()` for both token configs.
11. Sets `pendingUnpause[marketManager] = true`.

The reserve transfer happens before listing so `listTokens()` can call each cToken's `initializeDeposits()` path and pull the reserve from `ProtocolManagerDeployment`.

### Token Config Shape

`deployMarket()` accepts two `MarketManagerIsolated.TokenConfig` structs. The field order is:

```solidity
struct TokenConfig {
    address cToken;
    uint256 collRatio;
    uint256 collReqSoft;
    uint256 collReqHard;
    uint256 liqIncBase;
    uint256 liqIncHard;
    uint256 liqIncMin;
    uint256 liqIncMax;
    uint256 closeFactorBase;
    uint256 closeFactorMin;
    uint256 closeFactorMax;
    uint256 collateralCap;
    uint256 debtCap;
}
```

`ProtocolManagerDeployment` only checks the `cToken` address matches the explicit token argument. The full token-config validation is enforced by `MarketManagerIsolated.updateTokenConfig()`.

### One-Time Mint Unpause

`unpauseMarket()` consumes `pendingUnpause[marketManager]`, reads the market's listed tokens with `queryTokensListed()`, and calls `setMintPaused(token, false)` for every listed token.

This unpause is intentionally narrow:

* It only affects mint pause flags.
* It does not change collateralization, borrow, liquidation, redeem, or transfer pause flags.
* It can only be used while `pendingUnpause[marketManager]` is true.
* A second `unpauseMarket()` call for the same allowance reverts with `ProtocolManagerDeployment__NoPendingUnpause()`.

`revokeUnpause()` deletes the pending allowance without changing any market pause flags. It is callable by the deployment owner or by any address with market permissions, and it is idempotent.

### Deployment Scripts

`script/deployment/DeployProtocolManager_Deployment.s.sol` deploys this manager:

```solidity
function run(
    address registry,
    address ownerAddress
) external;
```

The script emits `ContractDeployed(address(pm), "ProtocolManagerDeployment")` through the shared deployment recorder.

After deployment, grant the new contract market permissions before using it:

```solidity
CentralRegistry.addMarketPermissions(protocolManagerDeployment);
```

`script/deployment/RevokeProtocolManagerDeploymentUnpause.s.sol` clears a pending allowance:

```solidity
function run(
    address protocolManagerDeployment,
    address marketManager
) external;
```

### Events and Errors

`ProtocolManagerDeployment` does not define custom events. Downstream calls emit market events such as `TokenListed`, `TokenActionPaused`, and `TokenConfigUpdated`.

| Error                                               | Meaning                                                                                                                            |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `ProtocolManagerDeployment__Unauthorized()`         | Caller is not authorized for the requested operation.                                                                              |
| `ProtocolManagerDeployment__ParametersAreInvalid()` | Constructor owner is zero, token config does not match token argument, market is unregistered, or an underlying asset has no code. |
| `ProtocolManagerDeployment__NoPendingUnpause()`     | `unpauseMarket()` was called without an active pending allowance.                                                                  |

### Integration Considerations

* The owner must approve `ProtocolManagerDeployment` for `77777` raw units of each underlying asset before calling `deployMarket()`.
* The target market manager must already be registered in `CentralRegistry`.
* The manager contract must already have market permissions.
* Expect minting to be paused immediately after `deployMarket()`.
* Use `pendingUnpause(marketManager)` to check whether the one-time mint unpause is still available.
* Use `revokeUnpause()` to clear stale allowances before relying on another actor's pause state.
* Do not treat `unpauseMarket()` as a general unpause tool. It only unpauses minting on the market's listed tokens.

### Callable Functions

#### Constructor

```solidity
constructor(ICentralRegistry cr, address _owner);
```

Validates the central registry, rejects a zero owner, and stores both immutable values.

#### Reads

```solidity
function BASE_UNDERLYING_RESERVE() external view returns (uint256);

function centralRegistry() external view returns (ICentralRegistry);

function owner() external view returns (address);

function pendingUnpause(address marketManager) external view returns (bool);
```

Use these reads to inspect the deployment authority, reserve requirement, registry, and pending mint-unpause state.

#### Market Deployment

```solidity
function deployMarket(
    address marketManager,
    address token0,
    address token1,
    MarketManagerIsolated.TokenConfig memory config0,
    MarketManagerIsolated.TokenConfig memory config1
) external;
```

Lists the two cTokens, initializes the cToken reserve deposits, pauses minting for both tokens, writes both token configs, and records a pending one-time mint unpause.

#### Mint Unpause

```solidity
function unpauseMarket(address marketManager) external;
```

Consumes the pending allowance for `marketManager` and unpauses minting for every token returned by `queryTokensListed()`.

#### Allowance Revocation

```solidity
function revokeUnpause(address marketManager) external;
```

Deletes the pending allowance for `marketManager`. The caller must be the owner or have market permissions in `CentralRegistry`.


# ProtocolManagerMassPause

### Overview

`ProtocolManagerDeployment` is a standalone deployment-phase manager for PMD market setup. It atomically lists a two-token isolated market, seeds the cToken dead-share reserves, pauses minting on both tokens, writes both token configs, and records a one-time mint-unpause allowance for that market.

The contract does not inherit `ProtocolManager` because it does not need period-limit tracking or a managed-address registry. Its surface is intentionally small: deploy a market, consume the one-time mint unpause, or revoke that unpause allowance.

### Core Architecture

| Item                  | Getter                      | Purpose                                                                                 |
| --------------------- | --------------------------- | --------------------------------------------------------------------------------------- |
| Central registry      | `centralRegistry()`         | Used to validate market-manager registration and market-permission callers.             |
| Deployment owner      | `owner()`                   | Address authorized to call `deployMarket()` and `unpauseMarket()`.                      |
| Reserve amount        | `BASE_UNDERLYING_RESERVE()` | `77777` raw units of each underlying asset, matching the cToken initialization reserve. |
| Pending unpause state | `pendingUnpause(address)`   | One-time mint-unpause allowance keyed by market manager.                                |

The deployed `ProtocolManagerDeployment` contract must have market permissions in `CentralRegistry` before it can call `listTokens()`, `setMintPaused()`, or `updateTokenConfig()` on `MarketManagerIsolated`.

### Deployment Flow

`deployMarket()` performs this sequence:

1. Checks that `msg.sender == owner`.
2. Checks that `config0.cToken == token0` and `config1.cToken == token1`.
3. Checks that `centralRegistry.isMarketManager(marketManager)` is true.
4. Reads each cToken's underlying asset through `ICToken.asset()`.
5. Rejects the call if either underlying address has no code.
6. Pulls `77777` raw units of each underlying from the caller.
7. Approves each cToken to spend its corresponding reserve amount.
8. Calls `MarketManagerIsolated.listTokens(token0, token1)`.
9. Pauses minting on both listed tokens.
10. Calls `updateTokenConfig()` for both token configs.
11. Sets `pendingUnpause[marketManager] = true`.

The reserve transfer happens before listing so `listTokens()` can call each cToken's `initializeDeposits()` path and pull the reserve from `ProtocolManagerDeployment`.

### Token Config Shape

`deployMarket()` accepts two `MarketManagerIsolated.TokenConfig` structs. The field order is:

```solidity
struct TokenConfig {
    address cToken;
    uint256 collRatio;
    uint256 collReqSoft;
    uint256 collReqHard;
    uint256 liqIncBase;
    uint256 liqIncHard;
    uint256 liqIncMin;
    uint256 liqIncMax;
    uint256 closeFactorBase;
    uint256 closeFactorMin;
    uint256 closeFactorMax;
    uint256 collateralCap;
    uint256 debtCap;
}
```

`ProtocolManagerDeployment` only checks the `cToken` address matches the explicit token argument. The full token-config validation is enforced by `MarketManagerIsolated.updateTokenConfig()`.

### One-Time Mint Unpause

`unpauseMarket()` consumes `pendingUnpause[marketManager]`, reads the market's listed tokens with `queryTokensListed()`, and calls `setMintPaused(token, false)` for every listed token.

This unpause is intentionally narrow:

* It only affects mint pause flags.
* It does not change collateralization, borrow, liquidation, redeem, or transfer pause flags.
* It can only be used while `pendingUnpause[marketManager]` is true.
* A second `unpauseMarket()` call for the same allowance reverts with `ProtocolManagerDeployment__NoPendingUnpause()`.

`revokeUnpause()` deletes the pending allowance without changing any market pause flags. It is callable by the deployment owner or by any address with market permissions, and it is idempotent.

### Events and Errors

`ProtocolManagerDeployment` does not define custom events. Downstream calls emit market events such as `TokenListed`, `TokenActionPaused`, and `TokenConfigUpdated`.

| Error                                               | Meaning                                                                                                                            |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `ProtocolManagerDeployment__Unauthorized()`         | Caller is not authorized for the requested operation.                                                                              |
| `ProtocolManagerDeployment__ParametersAreInvalid()` | Constructor owner is zero, token config does not match token argument, market is unregistered, or an underlying asset has no code. |
| `ProtocolManagerDeployment__NoPendingUnpause()`     | `unpauseMarket()` was called without an active pending allowance.                                                                  |

### Integration Considerations

* The owner must approve `ProtocolManagerDeployment` for `77777` raw units of each underlying asset before calling `deployMarket()`.
* The target market manager must already be registered in `CentralRegistry`.
* The manager contract must already have market permissions.
* Expect minting to be paused immediately after `deployMarket()`.
* Use `pendingUnpause(marketManager)` to check whether the one-time mint unpause is still available.
* Use `revokeUnpause()` to clear stale allowances before relying on another actor's pause state.
* Do not treat `unpauseMarket()` as a general unpause tool. It only unpauses minting on the market's listed tokens.

#### Market Deployment

```solidity
function deployMarket(
    address marketManager,
    address token0,
    address token1,
    MarketManagerIsolated.TokenConfig memory config0,
    MarketManagerIsolated.TokenConfig memory config1
) external;
```

Lists the two cTokens, initializes the cToken reserve deposits, pauses minting for both tokens, writes both token configs, and records a pending one-time mint unpause.

#### Mint Unpause

```solidity
function unpauseMarket(address marketManager) external;
```

Consumes the pending allowance for `marketManager` and unpauses minting for every token returned by `queryTokensListed()`.

#### Allowance Revocation

```solidity
function revokeUnpause(address marketManager) external;
```

Deletes the pending allowance for `marketManager`. The caller must be the owner or have market permissions in `CentralRegistry`.


# Old Cross-Chain Functionality

### Overview

The Curvance Protocol implements a sophisticated cross-chain architecture that enables seamless operation across multiple blockchains while maintaining protocol-wide consistency. This architecture enables unified governance, shared incentives, and the efficient distribution of rewards across the entire ecosystem.

### Core Architecture Components

The cross-chain system consists of several specialized components that work together:

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FvEwWGZMJrPMNiteal6HN%2FBlank%20diagram(28).png?alt=media&amp;token=c4eae857-84ae-4e70-a150-d5e721129e89" alt=""><figcaption></figcaption></figure>

#### Key Components

1. **CentralRegistry:** Serves as a single source of truth for each chain, containing protocol configuration and contract addresses.
2. **MessagingHub:** The unified communication layer that enables cross-chain message passing. It handles:
   1. Epoch coordination
   2. Fee distribution
   3. Token bridging
   4. veCVE lock migration
   5. Governance message propagation
3. **FeeManager:** Collects protocol fees and prepares them for cross-chain distribution.
4. **RewardManager:** Distributes rewards to veCVE holders based on their proportional lock amounts.
5. **VotingHub**: Coordinates token emission allocation across all chains based on governance decisions.

### Cross-Chain Communication

Curvance's cross-chain architecture relies on two primary technologies:

1. **Wormhole**
   1. Facilitates generic message passing between chains
   2. Provides **Cross-Chain Query (CCQ)** capability for state verification
   3. Powers the system's **VAA (Verifiable Action Approval)** verification
2. **Circle's CCTP (Cross-Chain Transfer Protocol)**
   1. Handles token transfers between chains
   2. Primarily used for fee and reward token movements

### Primary Data Flows

The Curvance Protocol has several critical cross-chain data flows:

#### 1. Epoch Execution and Fee Distribution

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2Fsp1E6xfBUG6gwTDgFcWj%2FBlank%20diagram(29).png?alt=media&amp;token=64e480b5-22b8-4b43-9f60-f0481eab1526" alt=""><figcaption></figcaption></figure>

This diagram illustrates the bi-weekly process of distributing protocol fees across all chains in the Curvance ecosystem:

1. A privileged operator initiates the process on one chain by querying veCVE lock points across all other chains using Wormhole CCQ.
2. After verification of chain data, the system pulls accumulated fees from the local FeeManager.
3. The protocol calculates each chain's share of fees based on the proportion of total veCVE points on that chain.
4. Fees are then transferred to destination chains using CCTP for the token transfer, alongside a Wormhole message carrying distribution instructions.
5. Each receiving chain's MessagingHub routes the received tokens to its RewardManager for distribution to veCVE holders proportionally.

This mechanism ensures fair fee distribution based on governance participation across all chains in the ecosystem.

#### 2. CVE / veCVE Cross-Chain Migration

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2Fm9ASSCcb4XlZA3tPdNf6%2FBlank%20diagram(30).png?alt=media&amp;token=feaff71a-31c5-4a9b-ad8f-5eeee5222153" alt=""><figcaption></figcaption></figure>

**CVE Bridging**

1. User initiates bridge on source chain
   1. Calls `bridge(recipient, dstChainId, amount, gasLimit)` on CVE contract
   2. Pays required message fee
2. Accounting & Message Prep
   1. CVE contract burns user's tokens (`_burn(msg.sender, amount)`)
   2. CVE calls MessagingHub's `bridgeToken()` with payloadType 5
   3. MessagingHub validates parameters (chain support, recipient address)
   4. MessagingHub calls Wormhole to send cross-chain message
3. Wormhole Message
   1. Wormhole network relays the message to destination chain
   2. Message contains recipient address and token amount
4. Accounting & Validation
   1. MessagingHub receives and validates the Wormhole message
   2. Checks that message hasn't been processed before (prevents replay)
   3. For payloadType 5, calls `completeBridge()` on destination CVE
   4. Destination CVE mints tokens to recipient
5. Result
   1. User now has equivalent CVE tokens on destination chain
   2. Total supply across chains remains constant (burned on source, minted on destination)

**veCVE Bridging**

1. User initiates lock bridge on source chain
   1. Calls `bridgeLock(lockIndex, bridgeData, rewardsData, params, aux)` on veCVE
   2. Provides destination chain info and continuousLock status preference
   3. Pays required message fee
2. Accounting & Message Prep
   1. Claims any pending rewards for user
   2. Updates user's points to reflect removed lock
   3. Burns user's veCVE tokens
   4. Removes lock entry from storage
   5. Calls `burnLockedTokens()` on CVE to burn underlying tokens
   6. veCVE calls MessagingHub's `bridgeToken()` with payloadType 4
   7. Encodes lock parameters (amount, continuous lock status)
   8. MessagingHub validates and sends Wormhole message
3. Wormhole Message
   1. Wormhole network relays the message to destination chain
   2. Message contains original owner, amount, and lock properties
4. Accounting & Validation
   1. MessagingHub receives and validates the Wormhole message
   2. For payloadType 4, calls `mintLockedTokens()` on destination CVE
   3. CVE mints tokens to MessagingHub
   4. MessagingHub approves veCVE to spend the tokens
   5. Calls `createLockFor()` on veCVE with same properties
5. Result
   1. User has equivalent veCVE lock on destination chain
   2. Lock maintains same continuous status as selected
   3. Total veCVE supply across chains remains consistent

#### 3. Todo: Emissions & Gauge flow

### State Machine

The Curvance cross-chain architecture implements several key states:

#### Epoch State Machine

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FAVfzJi6LlFhXIAW4m7Kb%2Fimage.png?alt=media&amp;token=91c28285-d86a-49b0-a42c-ba02f1cde904" alt=""><figcaption></figcaption></figure>

This diagram outlines the state transitions during a Curvance protocol epoch:

* **Epoch Start:** A new epoch begins based on the timestamp defined by `genesisEpoch + (epochNumber * EPOCH_DURATION)`.
* **Fee Collection & Trading Activity:** During the epoch, fees accumulate from protocol activity across all chains independently.
* **Cross-Chain Coordination:** Near epoch end, the protocol aggregates veCVE points across all chains using Wormhole CCQ to determine reward distribution.
* **Fee Distribution:** Collected fees are distributed pro-rata to each chain based on its proportion of total veCVE points, with tokens bridged using CCTP.
* **Epoch Completion:** The `nextEpochToDeliver` counter is incremented, allowing the system to track epochs sequentially and ensure proper distribution.

The epoch system operates in a strictly sequential manner to ensure consistency across chains.

### Fault Tolerance & Security Measures

The cross-chain architecture implements several safety mechanisms:

1. **Message Hash Verification**: Each cross-chain message is tracked by its hash to prevent replay attacks.
2. **Messaging Status Controls:** The messaging hub can be put into various states to control message creation and execution:
   * **Status 1:** Full operation (create and execute messages)
   * **Status 2:** Execute-only mode (no new messages)
   * **Status 3:** Complete pause (no operations)
3. **Guardian Validation:** Uses Wormhole guardians to validate cross-chain messages through VAA signatures.
4. **Chain Verification:** Messages are only accepted from known, registered chains configured in the Central Registry.
5. **Fallback Mechanisms:** If the system gets stuck, administrative overrides exist to ensure continued operation.

### Consistency Model

The Curvance Protocol maintains cross-chain consistency through:

1. **Synchronized Epochs:** All chains share a common epoch schedule, defined by the genesisEpoch and epochDuration values.
2. **Cross-Chain Queries:** The system uses Wormhole's CCQ to verify the state of remote chains before making critical decisions.
3. **Sequential Epoch Processing:** Each epoch must be processed in order, and all chains must catch up sequentially (tracked via nextEpochToDeliver).
4. **Verifiable Messages:** All cross-chain communications are verified through cryptographic signatures from Wormhole guardians.

### In Conclusion

The Curvance cross-chain architecture creates a unified, multi-chain DeFi protocol that maintains consistency and shared state across all supported networks. This design enables protocol-wide coordination for token emissions, fee distribution, and governance while allowing users to interact with the protocol on their preferred blockchain.


# Messaging Hub

## Overview

The Messaging Hub serves as the central communication layer of the Curvance Protocol, facilitating cross-chain operations between all supported blockchains. It coordinates epoch transitions, distributes protocol fees, manages token emissions, and facilitates the migration of user positions between chains.

### State Machine

The Messaging Hub operates in four states:

* **Inactive (0):** Not used (reverts if attempted)
* **Send Active (1):** Can send messages but not receive them
* **Fully Active (2):** Can send and receive messages
* **Emergency Paused (3):** All cross-chain messaging functionality is disabled

State transitions require appropriate permissions:

* Standard DAO permissions can pause the system
* Elevated permissions are required to unpause or modify messaging pathways

### Message Payload Types

The Messaging Hub defines a type system for cross-chain messages:

<table><thead><tr><th width="125.2166748046875">Payload Type</th><th>Purpose</th></tr></thead><tbody><tr><td>1</td><td>Basic fee transfer between chains</td></tr><tr><td>2</td><td>Gauge emission configuration</td></tr><tr><td>3</td><td>Epoch reward distribution</td></tr><tr><td>4</td><td>veCVE lock migration</td></tr><tr><td>5</td><td>CVE token bridging</td></tr></tbody></table>

## Cross-Chain Data Flows

#### 1. Epoch Coordination Flow

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2Fv6rn5FgYlJs1AdgYAHVk%2FBlank%20diagram(32).png?alt=media&amp;token=279649a9-fe9e-44f5-9d75-31b59b49e43d" alt=""><figcaption></figcaption></figure>

The Messaging Hub aggregates veCVE lock points across all chains using Wormhole's Cross-Chain Query (CCQ) system. This data drives protocol-wide reward distribution based on the proportional vote-escrow locking across the entire ecosystem.

#### 2. Fee Distribution Flow

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FEK5Z8zQ43HSGpxivd5n9%2FBlank%20diagram(33).png?alt=media&amp;token=7c86bbad-3d98-458f-a262-fd35e95d4c0f" alt=""><figcaption></figcaption></figure>

Protocol fees are collected by each chain's FeeManager and transferred via CCTP, with cross-chain coordination handled by Wormhole messaging. This enables a unified reward system where all fees contribute to protocol-wide incentives.

#### 3. Token Bridge Flow

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FSe3A4kbNYfSZ3eQ4CWET%2FBlank%20diagram(34).png?alt=media&amp;token=20af5425-28fd-42b0-b821-b3c5eb849429" alt=""><figcaption></figcaption></figure>

Users can bridge CVE tokens or migrate veCVE locks between chains. The architecture separates the lock representation from the underlying token, enabling governance power to move seamlessly across the network.

#### 4. Emission Configuration Flow

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FxqRu3nJReEx1ieoPhKl7%2FBlank%20diagram(36).png?alt=media&amp;token=7becf1ed-81a4-45c8-8ee1-2e618b7b481c" alt=""><figcaption></figcaption></figure>

Token emissions are determined by governance voting and distributed to each chain through the Messaging Hub. This ensures that incentives align with governance decisions across the entire protocol.

#### 5. Native Gas Storage for Cross-Chain Actions

The Messaging Hub manages native gas tokens to pay for cross-chain messaging fees across the entire protocol.

#### Gas Management Flow

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FvD9qUbbNXOpDv6vYwhEW%2FBlank%20diagram(42).png?alt=media&amp;token=b3efa5bb-c048-424a-92ae-bc01df5bd513" alt=""><figcaption></figcaption></figure>

* The contract holds native gas tokens to pay for cross-chain message delivery
* Gas cost depends on destination chain, payload size, and gas limit configuration
* Default gas limit (300,000) ensures sufficient resources on destination chains
* Gas fees are calculated through `quoteMessageFee` which includes:
  * Wormhole relayer fees for message delivery
  * Additional Wormhole core message publishing fees

This architecture allows the protocol to operate seamlessly across multiple chains while maintaining security, proper fee distribution, and efficient gas usage for all cross-chain operations.

### Integration with External Messaging Systems

The Messaging Hub acts as an abstraction layer over multiple underlying cross-chain communication systems:

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FVyDct4jZMN1j2JperWBR%2FBlank%20diagram(37).png?alt=media&amp;token=3fdb6c21-31a2-4db1-8ea5-2d343dc11f85" alt=""><figcaption></figcaption></figure>

* Wormhole handles general message passing and validation
* Circle's CCTP manages cross-chain stablecoin transfers
* Message Keys link CCTP transfers with Wormhole messages

### Message Execution Security

The Messaging Hub implements multiple security mechanisms:

1. **Message Deduplication:** All message hashes are recorded to prevent replay attacks
2. **Source Validation:** Messages are only processed from known messaging hubs on authorized chains
3. **Payload Type Validation:** Each payload type requires specific validation logic
4. **Chain ID Validation:** Messages are validated against registered chain IDs
5. **Status Controls:** The messaging status state machine prevents undesired message processing

### Cross-Chain Health Management

The architecture includes specialized handling for cross-chain synchronization issues:

* When a RewardManager is offline, rewards are sent to the DAO address
* If epoch progression is out of sync, the system routes funds to maintain protocol health
* Native gas tokens stored in the Messaging Hub ensure cross-chain actions can be funded

This resilient design ensures the protocol can maintain operations even when individual chains or components experience temporary issues.


# Fee Manager

### 1. Fee Collection

The FeeManager serves as the central collection point for protocol fees across the Curvance ecosystem. It acts as a unified hub for aggregating fees generated from various protocol operations.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FaTobbJOVtYcWVpiaJEFJ%2FBlank%20diagram(43).png?alt=media&amp;token=5c53e0e5-c40e-4d25-9030-23282fdb8599" alt=""><figcaption></figcaption></figure>

* **Protocol Revenue Streams:** Fees collected from lending/borrowing operations, liquidations, and other protocol services flow into the FeeManager
* **Reward Token Registry:** The contract maintains a registry of valid reward tokens that can be accepted as fees, enabling flexibility in fee collection

**Storage Architecture:**

* **rewardTokens:** Array of supported token addresses
* **rewardTokenInfo:** Mapping tracking token status (including OTC eligibility)

### Fee Transformation (Swaps/OTC)

Before cross-chain distribution, collected fees may need transformation into the protocol's primary fee token.

#### Fee Token Swaps

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FkujkabpvKeBqKpNYNabE%2FBlank%20diagram(44).png?alt=media&amp;token=34899809-934e-41eb-b3f2-0a3b901d1cb3" alt=""><figcaption></figcaption></figure>

* **Permissioned Swap Execution:** Authorized harvesters can initiate swaps through the multiSwap function
* **OTC Protection:** Tokens marked for OTC are protected from automated swaps
* **Token Verification:** Ensures input tokens are registered reward tokens and output is the fee token

#### DAO OTC Mechanism

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FBYVMYqAqvH9He3fyj7in%2FBlank%20diagram(45).png?alt=media&amp;token=39718ce3-0c6b-4719-8a80-d792fb561bcd" alt=""><figcaption></figcaption></figure>

* **Price Oracle Integration:** Uses Oracle Manager to determine fair market value for OTC transactions
* **Token Earmarking:** Tokens can be marked as "for OTC" (value=2), preventing them from being swapped
* **Price Protection:** Includes slippage control and deadline validation to ensure fair execution

### 3. Cross-Chain Fee Distribution

The cross-chain distribution of fees is coordinated between the FeeManager and MessagingHub.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FsXBSUeZ7CiVL9dg7JgsP%2FBlank%20diagram(46).png?alt=media&amp;token=5ea91ad2-0598-4cd2-8b4a-cad1b493dfc4" alt=""><figcaption></figcaption></figure>

#### Distribution Flow

1. **MessagingHub Pull:** The MessagingHub calls pullFees() to retrieve the collected fee tokens
2. **Fee Allocation:**
   1. Compounding fees are allocated to DAO address for harvester bot operations
   2. Remaining fees are sent to MessagingHub for cross-chain distribution
3. **Cross-Chain Transfer:**
   1. MessagingHub uses Circle's CCTP (Cross-Chain Transfer Protocol) and Wormhole for secure cross-chain messaging
   2. Tokens are distributed proportionally based on veCVE points across chains
4. **Epoch Management:**
   1. Rewards are distributed during epoch executions based on veCVE points on each chain
   2. Wormhole's Cross-Chain Query (CCQ) gathers data about locked tokens across chains

#### Technology Stack

* **Circle CCTP:** Used for transferring fee tokens across chains
* **Wormhole:** Provides the messaging infrastructure for cross-chain communications
* **Wormhole CCQ:** Enables querying of lock points data from other chains

### 4. Integration with 1inch/Solvers

FeeManager leverages external liquidity sources to optimize token swaps

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FdZWeM9AtEplnKL4fBB6R%2FBlank%20diagram(47).png?alt=media&amp;token=942335ea-f033-466d-bfc9-3a7d7016d328" alt=""><figcaption></figcaption></figure>

* **Offchain Solvers:** Integration with 1inch and potentially other solvers for optimized swap routing
* Swap Safety:
  * **`SwapperLib.swapSafe()`** enforces validation rules
  * External calldata checker verifies swap transactions
  * Token approvals are managed automatically
* **Permissioned Access:** Only authorized harvester addresses can execute swaps
* **Swap Verification:**
  * Validates input/output tokens match expected values
  * Strict swap parameter validation prevents malicious transactions

***

The FeeManager's cross-chain architecture enables Curvance to maintain a unified fee management system across multiple blockchains, ensuring proportional reward distribution and efficient token handling throughout the protocol ecosystem.


# Reward Manager

### 1. Biweekly Reward Cycle

The Curvance Protocol operates on a biweekly (2-week) epoch cycle for distributing rewards to veCVE holders across all supported chains. This process is coordinated by the MessagingHub contract which serves as the cross-chain communication layer.

Key aspects:

* Each epoch lasts exactly 2 weeks as defined by `EPOCH_DURATION` in the CentralRegistry
* The MessagingHub coordinates epoch transitions by querying veCVE points across all chains
* The RewardManager tracks which epoch's rewards have been delivered using `nextEpochToDeliver`
* State transitions occur when the MessagingHub calls `recordEpochRewards()` with the calculated reward amounts

State machine:

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2Fde47pBhPUYIdAQQ9nGSp%2FBlank%20diagram(38).png?alt=media&amp;token=e5438455-6e7c-45eb-89c7-0437cdf18be7" alt=""><figcaption></figcaption></figure>

### 2. Pro-Rata Distribution

Rewards are distributed proportionally across all chains based on the amount of veCVE locked on each chain.

Key aspects:

* The MessagingHub uses Wormhole's Cross-Chain Query (CCQ) to get accurate veCVE point counts from all chains
* Reward distribution is calculated as: `(Chain's veCVE Points / Total veCVE Points) * Total Rewards`
* Each chain receives a pro-rata share of the total protocol fees based on its proportion of total locked veCVE
* This creates an incentive for chains to attract veCVE lockers to increase their share of rewards

Data flow:

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FQipf1UvwxQR2bJoq2auo%2FBlank%20diagram(39).png?alt=media&amp;token=e4987973-0b94-4f65-b990-7b30f58f6712" alt=""><figcaption></figcaption></figure>

### 3. Reward Accumulation

The system allows users to accumulate rewards over multiple epochs without having to claim them immediately.

Key aspects:

* Each user has a `userNextClaimIndex` that tracks the next epoch they can claim rewards from
* `epochRewardsPerPoint` records the rewards allocated per veCVE point for each epoch
* Users can claim multiple epochs of accumulated rewards in a single transaction
* The formula for a user's rewards for an epoch is: `(User's veCVE Points * epochRewardsPerPoint[epoch]) / WAD`

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2Fut3XGTxHknH5lOFIHMSI%2FBlank%20diagram(40).png?alt=media&amp;token=ebc697e5-4edd-4960-9273-85f723307a4c" alt=""><figcaption></figcaption></figure>

### 4. USDC Distribution via CCTP/Wormhole

The cross-chain movement of rewards (primarily USDC) is handled through Circle's Cross-Chain Transfer Protocol (CCTP) and Wormhole's messaging system.

Key aspects:

* The protocol uses CCTP to transfer USDC between chains, ensuring secure token transfers
* Wormhole's automatic relayer system delivers cross-chain messages with execution guarantees
* The MessagingHub sends both USDC tokens via CCTP and reward metadata via Wormhole
* When a chain receives USDC, it routes the tokens to its local RewardManager

Cross-chain token flow:

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FhWq5VfH7oV9YAXylmcjH%2FBlank%20diagram(41).png?alt=media&amp;token=8011b181-6175-481f-9532-13a4df34b3bd" alt=""><figcaption></figcaption></figure>

This architecture ensures that rewards are efficiently distributed across chains while maintaining a consistent state of reward records throughout the entire protocol.


# Copy of Dynamic Liquidation Engine (DLE)

### Overview

The Dynamic Liquidation Engine (DLE) is a sophisticated liquidation management system implemented in the Curvance Protocol. Facilitating market collateral liquidations through a buffer-based approach with Atlas integration for OEV (OracleExtractable Value) capture.

### Technical Architecture

#### OEV Integration with Atlas

The contract integrates OEV (Oracle Extractable Value) capture:

* **OEV Auction Mechanism:** When price updates make positions liquidatable, an off-chain auction (running \~300ms) allows liquidators to bid for execution rights.
* **Atomic Execution:** Winning bids are executed immediately in the same block as an oracle update.
* **Bid Distribution:** Successful liquidation bids are distributed to Curvance Protocol through the account abstraction operation.
* **Collateral Targeting:** Each auction targets a specific collateral type, enforced through transient storage during transaction execution.

#### Auction Buffer System

The contract implements a liquidation buffer that gives auction transactions priority access to liquidations:

* **Buffer Mechanism:** A 10 basis point (0.1%) buffer gives auction transactions priority for liquidations.
* **Implementation:** When MEV-boosted liquidations are detected via transient storage, liquidation health checks apply the buffer as a discount to collateral value.
* **Benefits:**
  * Captures liquidations from non-oracle based changes as interest accrual or LST (Liquid Staking Token) yield distribution.
  * Compensates for execution latency.

#### Transient Storage Risk Parameters

Dynamic liquidation parameters are managed through transient storage:

* **Liquidation Penalty:** Configurable within min/max bounds set by governance.
* **Close Factor:** Determines what percentage of debt can be liquidated.
* **Transient Nature:** Parameters exist only for the duration of a transaction.
* **Validation:** All parameters undergo boundary validation before being applied.

#### Liquidation Types

The contract supports token-specific liquidations:

* **Token-Specific Liquidations:**
  * Targets individual collateral positions within an account.
  * Uses the `unlockAuctionForMarket()` combined with `_setTransientLiquidationConfig()` mechanism to specify which token can be liquidated.
  * Ideal for soft liquidations where only specific assets need adjustment.

#### Multi-Liquidation Support

The system is designed to efficiently process liquidations:

* **Batch Processing:** Can handle multiple liquidations in a single transaction.
* **Cached Pricing:** Retrieves asset prices once per transaction rather than per liquidation.
* **Gas Optimization:** Significantly reduces gas costs during liquidation cascades.
* **Debt Repayment Rollup:** Combines debt repayment and bad debt recognition into a single action (1,000 liquidations requires 1 debt token transfer, not 1,000 transfers).

### Dual-Oracle Architecture

Curvance's pricing system is designed with risk mitigation as its primary focus through a dual-oracle approach that enhances reliability and security.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2F1fhv1vrGar4KD1P04PPg%2FBlank%20diagram(49).png?alt=media&amp;token=abeae3c0-f573-4ac0-a145-532795e5d64b" alt=""><figcaption></figcaption></figure>

For each supported asset, Curvance can simultaneously integrate with two independent oracle providers. This creates redundancy and provides additional verification of asset prices, with the following benefits:

* **Enhanced security:** Mitigates risk from oracle failures or manipulation.
* **Continuous operation:** Ensures liquidations can proceed even in volatile markets.
* **Safety Buffer:** Creates a safety buffer when valuing collateral during distressed situations.

#### Price Selection Logic

When determining an asset's price, Curvance employs the "most safe" selection algorithm:

1. Each oracle reports its price for the asset.
2. The system applies sanity checks to both reported prices:
   1. Deviation from previous price must not exceed configured limits.
   2. Price must be above minimum threshold (non-zero).
   3. Oracle must have reported within the maximum allowable reporting window.
3. **For borrowable assets, the system selects the higher of the two valid prices.**
4. **For collateral assets, the system selects the lower of the two valid prices.**

<pre class="language-solidity"><code class="lang-solidity"><strong>// inside of _getLiquidationConfig()
</strong><strong>
</strong><strong>(tData.collateralSharesPrice, tData.debtUnderlyingPrice) =
</strong><strong>    CommonLib._oracleManager(centralRegistry)
</strong><strong>    .getPriceIsolatedPair(collateralToken, debtToken, 2);
</strong></code></pre>

This approach ensures that in liquidation scenarios, the protocol always errors on the side of protecting itself from bad debt, while giving borrowers the benefit of the most favorable valid price.

#### Validation Process

Liquidation attempts undergo multiple validation checks.

For example, we check if collateral is unlocked for auction transactions:

```solidity
// Will revert if this liquidation is an attempted auction liquidator
// and liquidator has chosen incorrect collateral or market.
// Pulls any relevant offchain liquidation configuration.
(tData.auctionBuffer, aData.liqInc, aData.closeFactor) =
    _checkLiquidationConfig(collateralToken);
```

#### Risk Mitigation

The system incorporates several safeguards:

* **Transient Storage:** Ensures parameters reset after each transaction.
* **Permission Checks:** Restricts access to orderflow auction functions to authorized addresses.
* **Collateral Locking:** Prevents liquidation of unauthorized collateral during Atlas transactions.

### Protocol Benefits

* **Value Capture:** Extracts MEV from liquidations that would otherwise go to third parties.
* **Liquidation Efficiency:** Ensures timely liquidations even during high volatility.
* **Gas Optimization:** Uses efficient code combined with transient storage for parameter management.
* **Graceful Degradation:** Protocol remains secure even if OEV auctions fail.

By implementing this sophisticated liquidation system, Curvance balances the needs of protocol security, liquidator incentives, and value capture, creating a powerful framework for position management across diverse market conditions.

***

## User Interaction Functions

#### statusOf()

**Description:** Determine `account`'s current status between collateral, debt, and additional liquidity.

**Contract:** MarketManager

**Function signature:**

```solidity
function statusOf(
    address account
) external returns (uint256, uint256, uint256) {
```

<table><thead><tr><th width="126.45001220703125">Type</th><th width="148.45001220703125">Name</th><th>Description</th></tr></thead><tbody><tr><td>address</td><td>account</td><td>The account to check liquidation status for.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="108.7166748046875">Type</th><th>Description</th></tr></thead><tbody><tr><td>uint256</td><td>The current total collateral amount of <code>account</code>.</td></tr><tr><td>uint256</td><td>The maximum debt amount of <code>account</code> can take out with their current collateral.</td></tr><tr><td>uint256</td><td>The current total borrow amount of <code>account</code>.</td></tr></tbody></table>

***

#### canLiquidate()

**Description:** Validates and processes batch liquidations for multiple accounts, calculating collateral seizure amounts, debt repayment, and bad debt based on account health and market parameters.

**Contract:** MarketManager

#### Function signature:

```solidity
function canLiquidate(
    uint256[] memory debtAmounts,
    address liquidator,
    address[] calldata accounts,
    IMarketManager.LiqAction memory action
) external returns (LiqResult memory, uint256[] memory);
```

<table><thead><tr><th width="118.63330078125">Type</th><th width="152.45001220703125">Name</th><th>Description</th></tr></thead><tbody><tr><td>address[]</td><td>debtAmounts</td><td>The amounts of outstanding debt the liquidator wishes to repay, in underlying assets, empty if intention is to liquidate maximum amount possible for each account.</td></tr><tr><td>address</td><td>liquidator</td><td>The address of the account trying to liquidate <code>accounts</code>.</td></tr><tr><td>address[]</td><td>accounts</td><td>The addresses of the accounts to be liquidated.</td></tr><tr><td>LiqAction</td><td>action</td><td>Instructions for a liquidation action.</td></tr></tbody></table>

**Return data:**

<table><thead><tr><th width="123.23333740234375">Type</th><th>Description</th></tr></thead><tbody><tr><td>LiqResult</td><td>Hypothetical results for a liquidation action.</td></tr><tr><td>uint256[]</td><td>An array containing the debt amounts to repay from <code>accounts</code>, in assets.</td></tr></tbody></table>

***


# Orderflow Auction System

## Overview

The Curvance orderflow auction system combines off-chain liquidation auctions with on-chain enforcement to enable:

* Dynamic liquidation penalty and close factor per auction.
* Priority access within a small solvency window via auction buffer.
* Efficient batch liquidations with cached pricing.
* Strict permissions and correctness guards via transient storage.

On-chain logic refers to this as 'auction-based liquidations' and integrates cleanly with any off-chain solver/auction stack (e.g. Atlas/Fastlane).

## System Architecture

The Curvance orderflow auction system integrates on-chain and off-chain components to enable efficient liquidations with dynamic liquidation sizes and penalty rates.

1. **Off-Chain**
   1. Price monitoring services detect changes.
   2. Auction framework coordinates liquidator bidding.
   3. Bundler constructs a single transaction for the winning sequence of liquidations.
2. **On-Chain**
   1. CentralRegistry unlocks the specific market for the current transaction.
   2. MarketManager handles liquidation execution and validates configuration.
   3. Dynamic Penalty System manages incentive and close factor via transient storage.
   4. Auction Buffer System provides priority access to liquidations.

These components work together to process liquidations with optimal penalty rates while maximizing value capture and minimizing bad debt risk.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2F7JiqnrJWdvMbuKVGwlcB%2FBlank%20diagram.png?alt=media&amp;token=8599ab58-6f00-4689-aceb-c95c05176464" alt=""><figcaption></figcaption></figure>

#### Key Features

* **Dynamic Liquidation Penalties:** Adjustable within per-token min/max ranges, set per-transaction via transient storage.
* **Transient Storage:** Parameters apply only within the current transaction then reset.
* **Oracle Integration:** Dual-oracle pathing with an isolated pair fetch price udring liquidation to prioritize risk mitigation.
* **Liquidation Buffer:** 10 bps advantage for auction transactions.
* **Efficient Batch Liquidations:** Single config/pricing fetch used across multiple liquidations in one transaction.

#### Auction workflow

1. **Oracle Update & Operation Collection:** Price feed updates create liquidation opportunities. Liquidators generate signed UserOps containing bid amounts and execution instructions.
2. **Auction & Bundling:** Auctioneer receives bids from liquidators in the form of signed AA (Account Abstraction) operations. The auctioneer then selects highest bidding operations and bundles them into a single atomic transaction with itself as `tx.origin`.
3. **Transaction Execution:** The bundled transaction executes on the Auctioneer Entrypoint contract, which:
   * Performs pre-execution balance checks.
   * Calls each liquidator's operation in descending bid order.
   * Verifies bid payment through post-execution balance checks.
   * Continues to the next highest bidder if a liquidator fails to pay.

This mechanism maximizes MEV capture while ensuring liquidations always complete in a timely manner.

### Core Dataflows

#### Price Update to Liquidation Flow

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FmsAEISrGAWRYjOyVrkEh%2FBlank%20diagram(26).png?alt=media&amp;token=283fed8e-2909-4e53-bd31-462eb7fc9184" alt=""><figcaption></figcaption></figure>

The journey from price change to liquidation execution follows these steps:

1. Price change detected off-chain.
2. Auction runs and selects winners.
3. Bundler submits a transaction that:
   1. Unlocks the market in CentralRegistry.
   2. Sets auction paramters + collateral unlock in MarketManager.
   3. Executes batch liquidations.

This auction typically completes within milliseconds off-chain before submitting the transaction.

#### Liquidation Buffer System

The Auction buffer system provides priority access to liquidations:

1. **Buffer Mechanism:** A 10 basis point (0.1%) buffer gives auction transactions priority for liquidations; implemented in BPS as 9990.
2. **Implementation:** When Auction transactions are detected via transient storage, liquidation health checks apply the buffer and shifts borderline positions into liquidable range in a narrow window.
3. **Benefits:**
   1. Captures liquidations from interest accrual.
   2. Handles LST (Liquid Staking Token) yield accumulation.
   3. Compensates for network latency.

```solidity
    /// @notice Buffer to ensure orderflow auction-based liquidations have
    ///         priority versus basic liquidations, in `BPS`.
    /// @dev 9990 = 99.9%. Multiplied then divided by `BPS` = 10 bps buffer.
    uint256 public constant AUCTION_BUFFER = 9990;
```

```solidity
   function _checkLiquidationConfig(
        address cToken
    ) internal view returns (uint256, uint256, uint256) {
        (address unlockedCToken, uint256 liqIncentive, uint256 closeFactor) =
            getTransientLiquidationConfig();

        bool unlockedMarket = centralRegistry.isMarketUnlocked();
        bool unlockedCollateral = unlockedCToken == cToken;

        if (unlockedMarket || unlockedCollateral) {
            // This is an attempted auction liquidation, and is configured
            // correctly so give them the auction priority buffer.
            if (unlockedMarket && unlockedCollateral) {
                return (AUCTION_BUFFER, liqIncentive, closeFactor);
            }

            // This is an attempted auction liquidation, but its misconfigured
            // and unauthorized because of this.
            revert MarketManager__UnauthorizedLiquidation();
        }

        // This is not an attempted auction liquidation, so approve the
        // liquidation, but without any offchain liquidation config values.
        return (0, 0, 0);
    }
```

#### Dynamic Penalty Dataflow

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FiJo7mOyhC28h8nAZ0xLB%2FBlank%20diagram(25).png?alt=media&amp;token=999c50c6-e4e1-496a-800d-16d6fbd764a2" alt=""><figcaption></figcaption></figure>

The dynamic penalty mechanism operates as follows:

1. Liquidators submit bids indicating desired liquidation incentive (stored as BPS + incentive) and close factor (BPS).
2. During the auction transaction, the authorized caller:
   1. Unlocks the market in CentralRegistry.
   2. Calls `setTransientLiquidationConfig(cToken, incentive, closeFactor)` on MarketManager, which:
      1. Validates bounds against token min/max.
      2. Packs collateral address, incentive, and close factor into one transient slot.
3. Liquidation executes using these transient values.
4. After the transaction, parameters reset automatically; they can also be explicitly reset mid-bundle if needed.

Per-transaction auction parameters are packed into a single transient word:

* \[0..159] unlocked collateral cToken address.
* \[160..175] liquidation incentive (BPS, stored as BPS + incentive).
* \[176..191] close factor (BPS).

```solidity
uint256 internal constant _TRANSIENT_LIQUIDATION_CONFIG_KEY
    = 0x1966ec4daf81281b2aba49348128e9b155301b8486bde131e0db16a52b730b82;
uint256 internal constant _BITMASK_COLLATERAL_UNLOCKED = (1 << 160) - 1;
uint256 internal constant _BITPOS_LIQ_INCENTIVE = 160;
uint256 internal constant _BITPOS_CLOSE_FACTOR = 176;
```

```solidity
    function setTransientLiquidationConfig(
        address cToken,
        uint256 incentive,
        uint256 closeFactor
    ) external {
        _checkAuctionPermissions();
        _checkIsListedToken(cToken);

         CurvanceToken memory c = _tokenConfig[cToken];

        // Make sure this token actually can be liquidated, by being
        // collateralizable in the first place.
        if (c.collRatio == 0) {
            revert MarketManager__UnauthorizedLiquidation();
        }

        // Validate `incentive` is within configured incentive bounds. This
        // also validates `incentive` is not > the 16 bits we have allocated.
        if (incentive < c.liqIncMin || incentive > c.liqIncMax) {
            _revert(_INVALID_PARAMETER_SELECTOR);
        }

        // Validate `closeFactor` is within configured allowed close factor
        // range. This also validates `closeFactor` is not > the 16 bits we
        // have allocated.
        if (closeFactor < c.closeFactorMin || closeFactor > c.closeFactorMax) {
            _revert(_INVALID_PARAMETER_SELECTOR);
        }

        uint256 liqConfig = uint256(uint160(cToken));
        assembly {
            // Mask `liqConfig` to the lower 160 bits, in case the upper bits
            // somehow are not clean.
            liqConfig := and(liqConfig, _BITMASK_COLLATERAL_UNLOCKED)
            // Equals: liqConfig | (incentive << _BITPOS_LIQ_INCENTIVE) |
            //         closeFactor << _BITPOS_CLOSE_FACTOR.
            liqConfig := or(
                liqConfig,
                or(
                    shl(_BITPOS_LIQ_INCENTIVE, incentive),
                    shl(_BITPOS_CLOSE_FACTOR, closeFactor)
                )  
            )

            tstore(_TRANSIENT_LIQUIDATION_CONFIG_KEY, liqConfig)
        }
    }
```

```solidity
    function resetTransientLiquidationConfig() external {
        _checkAuctionPermissions();

        /// @solidity memory-safe-assembly
        assembly {
            tstore(_TRANSIENT_LIQUIDATION_CONFIG_KEY, 0)
        }
    }
```

```solidity
    function getTransientLiquidationConfig() public view returns (
        address cTokenUnlocked,
        uint256 incentive,
        uint256 closeFactor
    ) {
        uint256 liqConfig;
        /// @solidity memory-safe-assembly
        assembly {
            liqConfig := tload(_TRANSIENT_LIQUIDATION_CONFIG_KEY)
        }

        cTokenUnlocked = address(uint160(liqConfig));
        incentive = uint16(liqConfig >> _BITPOS_LIQ_INCENTIVE);
        closeFactor = uint16(liqConfig >> _BITPOS_CLOSE_FACTOR);
    }
```

{% hint style="info" %}
Important unit notes:

* Liquidation incentive is represented as BPS + incentive (e.g. 10500 -> 5% incentive). Transient incentive must use this representation to pass range checks.
* Close factor is in BPS (0-10000).
  {% endhint %}

#### Fallback Mechanism

When auction parameters are not set (transient values are zero), the system uses the base liquidation incentive an interpolated close factor configured by governance, computed from the account's lFactor.

{% hint style="info" %}
**Note:** No 10 bps buffer is applied in non-auction liquidations.
{% endhint %}

```solidity
// In _getLiquidationConfig() inside of MarketManagerIsolated.sol
// fallback to base curve when transient is zero.

        // We only need to cache these variables if we did not receive close
        // factor/liquidation incentive from `getLiquidationConfig`.
        if (aData.closeFactor == 0 || aData.liqInc == 0) {
            aData.closeFactorBase = c.closeFactorBase;
            aData.closeFactorCurve = c.closeFactorCurve;
            aData.liqIncBase = c.liqIncBase;
            aData.liqIncCurve = c.liqIncCurve;
        }
```

```solidity
// In _canLiquidate() inside of MarketManagerIsolated.sol 
      
        // If this liquidation does not have offchain submitted
        // parameters then closeFactorCurve will not be 0. We know this since
        // closeFactorCurve is BPS - closeFactorBase and closeFactorBase is
        // limited to MAX_BASE_CFACTOR meaning closeFactorCurve cannot ever be
        // 0 unless we did not receive offchain parameters and we need to
        // calculate close factor and liquidation penalty onchain.
        if (aData.closeFactorCurve != 0) {
            aData.closeFactor = aData.closeFactorBase +
                _mulDiv(aData.closeFactorCurve, aData.lFactor, WAD);
            aData.liqInc = aData.liqIncBase +
                _mulDiv(aData.liqIncCurve, aData.lFactor, WAD);
        }
```

#### Market and Collateral Unlocking Mechanism

1. Market unlock: performed via CentralRegistry (transient, per-tx).
2. Collateral unlock + parameters: set in MarketManager (transient, per-tx).
3. Both must be set; otherwise, auction attempts revert as unauthorized.

```solidity
    function unlockAuctionForMarket(address marketToUnlock) external {
        if (!hasAuctionPermissions[msg.sender]) {
            revert CentralRegistry__Unauthorized();
        }

        // Validate that you're unlocking an approved market manager.
        if (!isMarketManager[marketToUnlock]) {
            revert CentralRegistry__InvalidParameter();
        }

        uint256 marketToUnlockUint = uint256(uint160(marketToUnlock));
        /// @solidity memory-safe-assembly
        assembly {
            tstore(_TRANSIENT_MARKET_UNLOCKED_KEY, marketToUnlockUint)
        }
    }
```

```solidity
    function isMarketUnlocked() public view returns (bool isUnlocked) {
        uint256 result;
        /// @solidity memory-safe-assembly
        assembly {
            result := tload(_TRANSIENT_MARKET_UNLOCKED_KEY)
        }

        // CASE: This is not an Auction tx, so allow all markets,
        // and return false, the caller is not approved for auction-based
        // liquidations. 
        if (result == 0) {
            return isUnlocked;
        }

        // True if the caller is approved for auction-based liquidations,
        // otherwise false.
        isUnlocked = uint256(uint160(msg.sender)) == result;
    }
```

### Batch Liquidation Support.

The system supports efficient multi-liquidation processing:

1. **Bulk Processing:** Can handle multiple liquidations in a single transaction.
2. **Cached Pricing:** Retrieves asset prices once per transaction rather than per liquidation.
3. **Consolidated Transfers:** Aggregates results and returns both seized shares and debt to repay into a single transfer.
4. **Gas Optimization:** Significantly reduces gas costs during liquidation cascades.

This design allows for processing thousands of liquidations in a single transaction, improving efficiency during market stress.

```solidity
        // In canLiquidate inside of MarketManagerIsolated.sol
        
        (TokenLiqData memory tData, AccountLiqData memory aData) =
            _getLiquidationConfig(action.collateralToken, action.debtToken);

        // Amounts array is empty since the max amount possible
        // will be liquidated.
        result.liquidatedShares = new uint256[](action.numAccounts);
        address cachedAccount;
        address priorAccount;
        for (uint256 i; i < action.numAccounts; ++i) {
        // ...rest of the function...
```

### Auction Mechanics

#### Atlas Auction Structure

The Atlas auction system is the core mechanism for capturing liquidation MEV in Curvance. When a liquidation opportunity arises, the system conducts a brief (typically 300ms) auction before submitting an on-chain transaction.

#### Bid Components

Each liquidation bid consists of these key elements:

* **Solver Address:** The liquidator's smart contract that will execute the liquidation.
* **Execution Data:** Calldata specifying which account to liquidate and method parameters.
* **Penalty Bid:** The liquidation penalty rate the liquidator is willing to accept.
* **Close Factor Bid:** The percentage of debt the liquidator wants to liquidate.
* **OEV Bid Amount:** Payment offered to the protocol and other stakeholders.
* **Signature:** Cryptographic verification of the bid's authenticity.

#### Bid Classification

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FNU93xK2MGLrH2YfhGrXO%2FBlank%20diagram(27).png?alt=media&amp;token=a7a82cba-e7dc-4c09-984b-1e7645104ecd" alt=""><figcaption></figcaption></figure>

The auction system automatically classifies bids into two distinct categories:

**Minimum Penalty Bids:**

* Uses the protocol-defined minimum penalty (e.g., 5%).
* Must include a positive OEV payment amount.
* Typically used during normal market conditions.

**Higher Penalty Bids:**

* Specifies a penalty above the minimum (e.g., 6-20%).
* No OEV payment required.
* Used during high volatility or significant slippage.

#### Bid Ranking Algorithm

The auction employs a specialized ranking system with these core rules:

**Primary Ranking by Penalty Tier:**

* Minimum penalty bids compete exclusively against other minimum penalty bids.
* Higher penalty bids compete based on the penalty rate offered.

**Secondary Ranking Rules:**

* For minimum penalty bids: Higher OEV payment receives priority.
* For higher penalty bids: Lower penalty rate receives priority.

**Tertiary Sorting:** When OEV payments or penalty rates are identical, bids are ordered by timestamp.

#### Ranking Examples

**Example 1: Minimum Penalty Competition**

Consider these bids at the minimum 5% penalty:

| Liquidator  | Penalty | OEV Payment | Timestamp |
| ----------- | ------- | ----------- | --------- |
| LiquidatorA | 5%      | 10 ETH      | 10:00:01  |
| LiquidatorB | 5%      | 8 ETH       | 10:00:00  |
| LiquidatorC | 5%      | 12 ETH      | 10:00:02  |

Ranking order: LiquidatorC → LiquidatorA → LiquidatorB (sorted by highest OEV payment).

**Example 2: Higher Penalty Competition**

Consider these bids with penalties above minimum:

| Liquidator  | Penalty | OEV Payment | Timestamp |
| ----------- | ------- | ----------- | --------- |
| LiquidatorA | 7%      | 0           | 10:00:01  |
| LiquidatorB | 8%      | 0           | 10:00:00  |
| LiquidatorC | 6%      | 0           | 10:00:02  |

Ranking order: LiquidatorC → LiquidatorA → LiquidatorB (sorted by lowest penalty).

**Example 3: Mixed Competition**

When both minimum and higher penalty bids exist:

| Liquidator  | Penalty | OEV Payment | Timestamp |
| ----------- | ------- | ----------- | --------- |
| LiquidatorA | 5%      | 10 ETH      | 10:00:01  |
| LiquidatorB | 7%      | 0           | 10:00:00  |
| LiquidatorC | 5%      | 12 ETH      | 10:00:02  |

Ranking will attempt LiquidatorC first (highest OEV at min penalty), then LiquidatorA (second highest OEV at min penalty), then LiquidatorB (lowest penalty among higher penalty bids).

#### Execution Flow

The Atlas transaction processes these bids sequentially:

* The highest-ranked bid's solver contract is called first.
* If execution succeeds, the transaction completes and value is distributed.
* If execution fails, the next-ranked bid is attempted.
* This continues until either a liquidation succeeds.

#### For Minimum Penalty Bids with OEV:

As specified in the DAppControl contract, the allocateValue hook distributes the OEV payment between:

* The bundler (who submits the transaction).
* Oracle (price feed provider).
* Fastlane (Operations Relay infrastructure).
* The Curvance treasury.
* The exact distribution percentages are configured by Curvance governance.
* OEV bids are only present when a solver wins with a penalty bid equal to the minimum penalty.

#### For Higher Penalty Bids:

* No OEV payment is included or distributed.
* All excess value from the higher penalty accrues to the liquidator.
* No value distribution to protocol or infrastructure stakeholders occurs.

This dual economic model aligns incentives effectively: during normal market conditions, the protocol captures value through OEV payments, while during stressed market conditions, liquidators retain more value to offset increased risk and ensure liquidations complete successfully.

#### Oracle Data Flow

1. **Push vs. Pull Model:**
   1. Atlas OEV requires a push-based oracle model where price updates trigger on-chain events.
   2. Each price update can initiate liquidation opportunities.
2. **Transaction Sequencing:**
   1. When an oracle update transaction lands on-chain, it may trigger liquidations.
   2. Atlas integrates with the oracle update process to capture OEV (Optimal Extractable Value).
3. **Oracle Compatibility:**&#x20;
   1. Designed to work with any push-based oracle.
   2. RedStone push feeds are the primary integration target.
   3. Support for LST (Liquid Staking Token) redemption rate oracles.

### Data Storage Model

The system employs two distinct storage layers:

**Permanent Storage:**

* Penalty ranges (min, max, default values).
* Account health statuses.
* System configuration parameters.

**Transient Storage:**

* Dynamic penalty values during Atlas transactions.
* Close factor values.
* Collateral unlock status.
* Values automatically reset after transaction completion.

This separation ensures critical data persists while dynamic values remain ephemeral.

***

Resources:

Atlas Docs: <https://atlas-docs.pages.dev/atlas/introduction>


# Bad Debt Socialization

### Overview

Bad Debt Socialization is a critical risk management mechanism in the Curvance Protocol that handles scenarios where a borrower's collateral value falls below their outstanding debt. Rather than leaving the protocol with uncovered losses, this mechanism distributes the shortfall equitably among all lenders in the affected market, preserving system solvency while minimizing individual impact.

### Key Components

#### System Actors

* **Liquidators:** External agents who repay a portion of defaulted debt in exchange for collateral.
* **Borrowers:** Users with debt positions that may become undercollateralized.
* **Lenders:** eToken holders who collectively absorb any shortfall from liquidations.
* **Market Manager:** Orchestrates the liquidation and bad debt socialization process.

### Process Flow

#### 1. Bad Debt Detection

The system identifies positions where collateral value is insufficient to cover debt:

```
Collateral Value < (Debt * Liquidation Incentive) = Bad Debt Condition
```

#### 2. Asset-Specific Liquidation

When bad debt is detected:

* Each collateral asset is evaluated individually for liquidation.
* Liquidations occur on a per-asset basis rather than requiring full account liquidation.
* Multiple liquidations can be processed efficiently in a single transaction.

#### 3. Socialization Execution

When a liquidator executes the bad debt liquidation:

1. The system calculates total debt to be closed for the specific asset.
2. Determines how much can be repaid via the liquidator's token transfer.
3. Calculates the remainder as bad debt to be socialized.
4. The shortfall is distributed proportionally across all lenders of that asset.

```
Bad Debt = (Total Account Debt * Liquidation Incentive) - Collateral Value
Or, more simply:
Bad Debt = Total Account Debt - Liquidator Repayment
```

For example, if a borrower has $900 in debt with $850 in collateral during a liquidation with 5% liquidation incentive, lenders collectively absorb $95 (900 \* 1.05 - 850 = 945 - 850 = 95) of the debt as a loss. If the borrower is liquidated across two liquidations, 25% initially then the remaining 75%, borrowers would recognize $23.75 and then $71.25 as bad debt across the liquidations.

#### 4. State Transitions

The process follows these state transitions:

* Normal Operation → Bad Debt Detection: When collateral falls below debt.
* Bad Debt Detection → Asset Liquidation: Individual asset liquidation is triggered.
* Asset Liquidation → Socialization: Execution of liquidation with partial repayment.
* Socialization → Normal Operation: System returns to normal state with debt cleared.

### Technical Implementation

The socialization mechanism operates through a coordinated interaction between:

1. **Debt Calculation:** For each liquidation, the system:
   1. Calculates the total debt to be closed.
   2. Determines how much can be repaid through liquidation transfers.
   3. Subtracts this from total debt to find the bad debt amount.
2. **Efficient Processing:**
   1. Multiple liquidations can be batched in a single transaction.
   2. Gas optimization by consolidating repayments and bad debt calculations.
3. **eToken Integration:**
   1. The eToken contract recognizes unpaid debt by adjusting `totalBorrows` .
   2. This maintains the exchange rate mechanism while distributing losses.
4. **Atlas Integration:**
   1. Atlas prioritizes liquidations to minimize bad debt through efficient market mechanisms.
   2. Buffer system ensures Atlas gets priority for executing liquidations.
   3. Dynamic penalty system incentivizes liquidators appropriately based on market conditions.

### Security Considerations

* **Gas Efficiency:** Optimized implementation handles thousands of liquidations efficiently, even during market stress.
* **Edge Cases:** System handles rounding issues with conservative approaches that favor protocol solvency.
* **Transparent Tracking:** Bad debt events are fully transparent with on-chain events.

This streamlined mechanism ensures the protocol remains solvent even in extreme market conditions while distributing any unavoidable losses fairly among participants, with minimal gas costs and maximum transparency.


# Old Borrowing & Repayment

saving so i can use as a template for StrategyCTokens

This guide demonstrates how to integrate with Curvance Protocol using JavaScript and ethers.js v5.7, with specific examples using the shMON for collateral and USDC for lending.

{% hint style="info" %}
The MarketManager coordinates all cTokens, for a deep dive check out this article: [Market Manager](/app/developer-docs/lending-protocol/market-manager)
{% endhint %}

## Understanding Curvance Token Types

Curvance has two primary token types that together make up the protocol's market infrastructure:

* **pTokens (Position Tokens):** Used for collateral positions. These are ERC4626-compliant vault tokens that represent a user's collateral. Many pTokens are yield-optimized, automatically compounding underlying yields for depositors.
* **eTokens (Earn Tokens):** Used for lending positions. These tokens represent a user's deposits that are being lent out.
* **cTokens (Curvance Tokens):** The collective term for all variants of cTokens.

#### Yield-Optimized pTokens: How They Work

Yield-optimized pTokens like ConvexSTETH\_ETH2PoolPToken provide automatic yield generation for depositors:

1. **Deposit Process:** Users deposit Curve stETH-ETH LP tokens into the pToken.
2. **Behind the Scenes:** The pToken stakes these LP tokens in Convex Finance.
3. **Yield Generation:** The staked position earns CRV and CVX rewards from Convex.
4. **Harvesting:** Periodically, these rewards are harvested, swapped to ETH, and used to mint more LP tokens.
5. **Compounding:** New LP tokens are staked back in Convex, increasing the total assets.
6. **User Benefit:** The value of users' shares increases over time as yields accumulate.

This automated yield optimization happens regardless of whether the position is being used as collateral.

#### Setting Up Your Integration

First, install ethers.js v5.7:

```bash
npm install ethers@5.7.2
```

You'll need to define your contract addresses and ABIs. Here's a simplified example for the most important ones:

```javascript
const ADDRESSES = {
  STETH_ETH_LP: "0x21E27a5E5513D6e65C4f830167390997aA84843a",
  USDC: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  CONVEX_STETH_ETH_PTOKEN: "0x..." // Replace with actual pToken address
  EUSDC: "0x..." // Replace with actual eToken address
};
```

## Important Considerations Before Depositing

Before sending any transactions, check these important protocol values by calling `mintPaused()` in the MarketManager contract.

1. **Protocol Status:** Verify if minting of the pToken is not paused:

Call `mintPaused()` with the following arguments. Returning a 0 or 1 if not paused, else indicating that minting is paused.

<table><thead><tr><th width="109.20001220703125">Type</th><th width="469.98333740234375">Description</th></tr></thead><tbody><tr><td>address</td><td>Address of the pToken to check the minting status of.</td></tr></tbody></table>

Implementation:

```javascript
const marketManager = new ethers.Contract(MARKET_MANAGER_ADDRESS, MARKET_MANAGER_ABI, provider);
const isPaused = await marketManager.mintPaused(CONVEX_STETH_ETH_PTOKEN);
```

2. **Collateral Caps:** Check if the asset has reached its collateral cap by calling `collateralCaps()`, and `collateralPosted()` in the MarketManager contract:

Calling `collateralCaps()` using these function arguments, returning uint256 indicating the maximum amounts of pTokens that can be posted as collateral:

<table><thead><tr><th width="109.20001220703125">Type</th><th width="469.98333740234375">Description</th></tr></thead><tbody><tr><td>address</td><td>Address of the pToken to check the max amount of shares that can be posted as collateral.</td></tr></tbody></table>

Calling `collateralPosted()` using these function arguments, returning uint256 indicating the current amount of pTokens currently posted as collateral:

<table><thead><tr><th width="109.20001220703125">Type</th><th width="469.98333740234375">Description</th></tr></thead><tbody><tr><td>address</td><td>Address of the pToken to check the amount of collateral posted.</td></tr></tbody></table>

Implementation:

```javascript
const cap = await marketManager.collateralCaps(PTOKEN_ADDRESS);
const posted = await marketManager.collateralPosted(PTOKEN_ADDRESS)
const capReached = posted.gte(cap) && !cap.isZero();
```

3. **Minimum Hold Period:** Be aware that Curvance implements a 20-minute minimum hold period for deposits to mitigate flash loan attacks.
4. **Yield Optimization:** For yield-optimized pTokens, understand that your share value increases over time rather than the number of shares.

#### Best Practices for Production Implementations

1. **Error Handling:** Implement proper error handling for all transactions.
2. **Gas Estimation:** Use estimateGas before sending transactions to ensure proper gas limits.
3. **Approval Checking:** Check existing allowances before sending approval transactions.
4. **Transaction Monitoring:** Implement logic to track transaction status after submission.
5. **Read-Only Checks:** Use read-only calls to validate operations before sending transactions.

By following this guide, you'll be able to integrate with Curvance's deposit functionality for both yield-optimized collateral positions and lending positions using JavaScript and ethers.js v5.7.


# Gauge Reward System (WIP)


# Cross-Chain Functionality

### Overview

The Curvance Protocol implements a sophisticated cross-chain architecture that enables seamless operation across multiple blockchains while maintaining protocol-wide consistency. This architecture enables unified governance, shared incentives, and the efficient distribution of rewards across the entire ecosystem.

### Core Architecture Components

The cross-chain system consists of several specialized components that work together:

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FvEwWGZMJrPMNiteal6HN%2FBlank%20diagram(28).png?alt=media&amp;token=c4eae857-84ae-4e70-a150-d5e721129e89" alt=""><figcaption></figcaption></figure>

#### Key Components

1. **CentralRegistry:** Serves as a single source of truth for each chain, containing protocol configuration and contract addresses.
2. **MessagingHub:** The unified communication layer that enables cross-chain message passing. It handles:
   1. Epoch coordination
   2. Fee distribution
   3. Governance message propagation
3. **FeeManager:** Collects protocol fees and prepares them for cross-chain distribution.
4. **RewardManager:** Distributes rewards to entitled parties.
5. **VotingHub**: Coordinates token emission allocation across all chains based on governance decisions.

### Cross-Chain Communication

Curvance's cross-chain architecture relies on two primary technologies:

1. **Wormhole**
   1. Facilitates generic message passing between chains.
   2. Provides **Cross-Chain Query (CCQ)** capability for state verification.
   3. Powers the system's **VAA (Verifiable Action Approval)** verification.
2. **Circle's CCTP (Cross-Chain Transfer Protocol)**
   1. Handles token transfers between chains.
   2. Primarily used for fee and reward token movements.

### Primary Data Flows

The Curvance Protocol has several critical cross-chain data flows:

#### 1. Epoch Execution and Fee Distribution

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FrlvTBttr5Za0l5nwCZZK%2FBlank%20diagram(1).png?alt=media&amp;token=f7efd64f-4a9f-492f-8576-6bec91e13c20" alt=""><figcaption></figcaption></figure>

This diagram illustrates the bi-weekly process of distributing protocol fees across all chains in the Curvance ecosystem:

1. A privileged operator initiates the process on one chain by querying points across all other chains using Wormhole CCQ.
2. After verification of chain data, the system pulls accumulated fees from the local FeeManager.
3. The protocol calculates each chain's share of fees based on the proportion of total points on that chain.
4. Fees are then transferred to destination chains using CCTP for the token transfer, alongside a Wormhole message carrying distribution instructions.
5. Each receiving chain's MessagingHub routes the received tokens to its RewardManager for distribution to point accumulators proportionally.

This mechanism ensures fair fee distribution based on governance participation across all chains in the ecosystem.

### State Machine

The Curvance cross-chain architecture implements several key states:

#### Epoch State Machine

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2Fyf1kR2oEBYZLspoq19sk%2Fepoch.png?alt=media&amp;token=49db49f3-d27d-405e-a541-47ffb4080763" alt=""><figcaption></figcaption></figure>

This diagram outlines the state transitions during a Curvance protocol epoch:

* **Epoch Start:** A new epoch begins based on the timestamp defined by `genesisEpoch + (epochNumber * EPOCH_DURATION)`.
* **Fee Collection & Trading Activity:** During the epoch, fees accumulate from protocol activity across all chains independently.
* **Cross-Chain Coordination:** Near epoch end, the protocol aggregates points across all chains using Wormhole CCQ to determine reward distribution.
* **Fee Distribution:** Collected fees are distributed pro-rata to each chain based on its proportion of total points, with tokens bridged using CCTP.
* **Epoch Completion:** The `nextEpochToDeliver` counter is incremented, allowing the system to track epochs sequentially and ensure proper distribution.

The epoch system operates in a strictly sequential manner to ensure consistency across chains.

### Fault Tolerance & Security Measures

The cross-chain architecture implements several safety mechanisms:

1. **Message Hash Verification**: Each cross-chain message is tracked by its hash to prevent replay attacks.
2. **Messaging Status Controls:** The messaging hub can be put into various states to control message creation and execution:
   * **Status 1:** Full operation (create and execute messages).
   * **Status 2:** Execute-only mode (no new messages).
   * **Status 3:** Complete pause (no operations).
3. **Guardian Validation:** Uses Wormhole guardians to validate cross-chain messages through VAA signatures.
4. **Chain Verification:** Messages are only accepted from known, registered chains configured in the Central Registry.
5. **Fallback Mechanisms:** If the system gets stuck, administrative overrides exist to ensure continued operation.

### Consistency Model

The Curvance Protocol maintains cross-chain consistency through:

1. **Synchronized Epochs:** All chains share a common epoch schedule, defined by the genesisEpoch and epochDuration values.
2. **Cross-Chain Queries:** The system uses Wormhole's CCQ to verify the state of remote chains before making critical decisions.
3. **Sequential Epoch Processing:** Each epoch must be processed in order, and all chains must catch up sequentially (tracked via nextEpochToDeliver).
4. **Verifiable Messages:** All cross-chain communications are verified through cryptographic signatures from Wormhole guardians.

### In Conclusion

The Curvance cross-chain architecture creates a unified, multi-chain DeFi protocol that maintains consistency and shared state across all supported networks. This design enables protocol-wide coordination for token emissions, fee distribution, and governance while allowing users to interact with the protocol on their preferred blockchain.


# Messaging Hub

## Overview

The Messaging Hub serves as the central communication layer of the Curvance Protocol, facilitating cross-chain operations between all supported blockchains. It coordinates epoch transitions, distributes protocol fees, manages token emissions, and facilitates the migration of user positions between chains.

### State Machine

The Messaging Hub operates in four states:

* **Inactive (0):** Not used (reverts if attempted).
* **Send Active (1):** Can send messages but not receive them.
* **Fully Active (2):** Can send and receive messages.
* **Emergency Paused (3):** All cross-chain messaging functionality is disabled.

State transitions require appropriate permissions:

* Standard DAO permissions can pause the system.
* Elevated permissions are required to unpause or modify messaging pathways.

### Message Payload Types

The Messaging Hub defines a type system for cross-chain messages:

<table><thead><tr><th width="125.2166748046875">Payload Type</th><th>Purpose</th></tr></thead><tbody><tr><td>1</td><td>Basic fee transfer between chains.</td></tr><tr><td>2</td><td>Gauge emission configuration.</td></tr><tr><td>3</td><td>Epoch reward distribution.</td></tr></tbody></table>

## Cross-Chain Data Flows

#### 1. Epoch Coordination Flow

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2Fv6rn5FgYlJs1AdgYAHVk%2FBlank%20diagram(32).png?alt=media&amp;token=279649a9-fe9e-44f5-9d75-31b59b49e43d" alt=""><figcaption></figcaption></figure>

The Messaging Hub aggregates points across all chains using Wormhole's Cross-Chain Query (CCQ) system. This data drives protocol-wide reward distribution based on the proportional vote-escrow locking across the entire ecosystem.

#### 2. Fee Distribution Flow

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FEK5Z8zQ43HSGpxivd5n9%2FBlank%20diagram(33).png?alt=media&amp;token=7c86bbad-3d98-458f-a262-fd35e95d4c0f" alt=""><figcaption></figcaption></figure>

Protocol fees are collected by each chain's FeeManager and transferred via CCTP, with cross-chain coordination handled by Wormhole messaging. This enables a unified reward system where all fees contribute to protocol-wide incentives.

#### 3. Emission Configuration Flow

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FxqRu3nJReEx1ieoPhKl7%2FBlank%20diagram(36).png?alt=media&amp;token=7becf1ed-81a4-45c8-8ee1-2e618b7b481c" alt=""><figcaption></figcaption></figure>

Token emissions are determined by governance voting and distributed to each chain through the Messaging Hub. This ensures that incentives align with governance decisions across the entire protocol.

#### 4. Native Gas Storage for Cross-Chain Actions

The Messaging Hub manages native gas tokens to pay for cross-chain messaging fees across the entire protocol.

#### Gas Management Flow

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FvD9qUbbNXOpDv6vYwhEW%2FBlank%20diagram(42).png?alt=media&amp;token=b3efa5bb-c048-424a-92ae-bc01df5bd513" alt=""><figcaption></figcaption></figure>

* The contract holds native gas tokens to pay for cross-chain message delivery.
* Gas cost depends on destination chain, payload size, and gas limit configuration.
* Default gas limit (300,000) ensures sufficient resources on destination chains.
* Gas fees are calculated through `quoteMessageFee` which includes:
  * Wormhole relayer fees for message delivery.
  * Additional Wormhole core message publishing fees.

This architecture allows the protocol to operate seamlessly across multiple chains while maintaining security, proper fee distribution, and efficient gas usage for all cross-chain operations.

### Integration with External Messaging Systems

The Messaging Hub acts as an abstraction layer over multiple underlying cross-chain communication systems:

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FVyDct4jZMN1j2JperWBR%2FBlank%20diagram(37).png?alt=media&amp;token=3fdb6c21-31a2-4db1-8ea5-2d343dc11f85" alt=""><figcaption></figcaption></figure>

* Wormhole handles general message passing and validation.
* Circle's CCTP manages cross-chain stablecoin transfers.
* Message Keys link CCTP transfers with Wormhole messages.

### Message Execution Security

The Messaging Hub implements multiple security mechanisms:

1. **Message Deduplication:** All message hashes are recorded to prevent replay attacks.
2. **Source Validation:** Messages are only processed from known messaging hubs on authorized chains.
3. **Payload Type Validation:** Each payload type requires specific validation logic
4. **Chain ID Validation:** Messages are validated against registered chain IDs..
5. **Status Controls:** The messaging status state machine prevents undesired message processing

### Cross-Chain Health Management

The architecture includes specialized handling for cross-chain synchronization issues:

* When a RewardManager is offline, rewards are sent to the DAO address.
* If epoch progression is out of sync, the system routes funds to maintain protocol health.
* Native gas tokens stored in the Messaging Hub ensure cross-chain actions can be funded.

This resilient design ensures the protocol can maintain operations even when individual chains or components experience temporary issues.


# Fee Manager

### 1. Fee Collection

The FeeManager serves as the central collection point for protocol fees across the Curvance ecosystem. It acts as a unified hub for aggregating fees generated from various protocol operations.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FaTobbJOVtYcWVpiaJEFJ%2FBlank%20diagram(43).png?alt=media&amp;token=5c53e0e5-c40e-4d25-9030-23282fdb8599" alt=""><figcaption></figcaption></figure>

* **Protocol Revenue Streams:** Fees collected from lending/borrowing operations, liquidations, and other protocol services flow into the FeeManager.
* **Reward Token Registry:** The contract maintains a registry of valid reward tokens that can be accepted as fees, enabling flexibility in fee collection.

**Storage Architecture:**

* **rewardTokens:** Array of supported token addresses.
* **rewardTokenInfo:** Mapping tracking token status (including OTC eligibility).

### Fee Transformation (Swaps/OTC)

Before cross-chain distribution, collected fees may need transformation into the protocol's primary fee token.

#### Fee Token Swaps

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FkujkabpvKeBqKpNYNabE%2FBlank%20diagram(44).png?alt=media&amp;token=34899809-934e-41eb-b3f2-0a3b901d1cb3" alt=""><figcaption></figcaption></figure>

* **Permissioned Swap Execution:** Authorized harvesters can initiate swaps through the multiSwap function.
* **OTC Protection:** Tokens marked for OTC are protected from automated swaps.
* **Token Verification:** Ensures input tokens are registered reward tokens and output is the fee token.

#### DAO OTC Mechanism

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FBYVMYqAqvH9He3fyj7in%2FBlank%20diagram(45).png?alt=media&amp;token=39718ce3-0c6b-4719-8a80-d792fb561bcd" alt=""><figcaption></figcaption></figure>

* **Price Oracle Integration:** Uses Oracle Manager to determine fair market value for OTC transactions.
* **Token Earmarking:** Tokens can be marked as "for OTC" (value=2), preventing them from being swapped.
* **Price Protection:** Includes slippage control and deadline validation to ensure fair execution.

### 3. Cross-Chain Fee Distribution

The cross-chain distribution of fees is coordinated between the FeeManager and MessagingHub.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FsXBSUeZ7CiVL9dg7JgsP%2FBlank%20diagram(46).png?alt=media&amp;token=5ea91ad2-0598-4cd2-8b4a-cad1b493dfc4" alt=""><figcaption></figcaption></figure>

#### Distribution Flow

1. **MessagingHub Pull:** The MessagingHub calls pullFees() to retrieve the collected fee tokens
2. **Fee Allocation:**
   1. Compounding fees are allocated to DAO address for harvester bot operations.
   2. Remaining fees are sent to MessagingHub for cross-chain distribution.
3. **Cross-Chain Transfer:**
   1. MessagingHub uses Circle's CCTP (Cross-Chain Transfer Protocol) and Wormhole for secure cross-chain messaging.
   2. Tokens are distributed proportionally based on points across chains.
4. **Epoch Management:**
   1. Rewards are distributed during epoch executions based on points on each chain.
   2. Wormhole's Cross-Chain Query (CCQ) gathers data about locked tokens across chains.

#### Technology Stack

* **Circle CCTP:** Used for transferring fee tokens across chains.
* **Wormhole:** Provides the messaging infrastructure for cross-chain communications.
* **Wormhole CCQ:** Enables querying of lock points data from other chains.

### 4. Integration with 1inch/Solvers

FeeManager leverages external liquidity sources to optimize token swaps.

<figure><img src="https://4091724113-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsJ5CLOxFjcZYTs5pEZoN%2Fuploads%2FdZWeM9AtEplnKL4fBB6R%2FBlank%20diagram(47).png?alt=media&amp;token=942335ea-f033-466d-bfc9-3a7d7016d328" alt=""><figcaption></figcaption></figure>

* **Offchain Solvers:** Integration with 1inch and potentially other solvers for optimized swap routing
* Swap Safety:
  * **`SwapperLib.swapSafe()`** enforces validation rules.
  * External calldata checker verifies swap transactions.
  * Token approvals are managed automatically.
* **Permissioned Access:** Only authorized harvester addresses can execute swaps.
* **Swap Verification:**
  * Validates input/output tokens match expected values.
  * Strict swap parameter validation prevents malicious transactions.

***

The FeeManager's cross-chain architecture enables Curvance to maintain a unified fee management system across multiple blockchains, ensuring proportional reward distribution and efficient token handling throughout the protocol ecosystem.




---

[Next Page](https://docs.curvance.com/app/llms-full.txt/1)

