Only this pageAll pages
Powered by GitBook
1 of 57

Curvance

Protocol Overview

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Security

Loading...

Miscellaneous

Loading...

Loading...

Loading...

Developer Docs

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

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 LSTs, LRTs, stablecoins, or LP tokens. 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. This reduces liquidation overhead and enables near-instant risk offloading during volatility.

Native Looping and Scaling

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

Dual Oracles & Price Guards

Two‑source pricing with heartbeat checks, plus adaptive price guards that cap spikes and reject outliers—boosting price resilience, reducing manipulation risk, and preventing false liquidations.

Developer Plugins

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

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.

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

Curvance is purpose-built to bring high-efficiency lending to DeFi. The current implementation supports stablecoins, WETH, WBTC, Monad-native LSTs, and select LRTs. The system is optimized to offer deep liquidity, high LTVs, and dynamic risk processing for assets most relevant to the ecosystem at launch.

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 on assets like WETH, WBTC, and Monad LSTs without exposing the broader system to concentrated risk.

Assets native to Monad, such as LSTs and eventually Pendle PT variants, can be deployed into markets that suit their risk curve. Lower-risk assets 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. At launch on Monad, deposits such as stablecoins, WETH, WBTC, LSTs, and LRTs 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.

  • Vaults holding LP tokens, allowing users to lend against pooled liquidity positions while still earning underlying AMM or gauge rewards.

  • Native 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.

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:

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.

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

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

  • Borrow while retaining yield on collateral

  • Support for stablecoins, WETH, WBTC, LSTs and LRTs on Monad

  • One click leverage and strategy execution through position manager tools

Token Approval Management

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.

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.

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 cross-chain money market protocol that enables users to deposit collateral, borrow assets, and participate in governance across multiple blockchains. The protocol features:

  • Isolated risk environments for different asset classes.

  • Dynamic Liquidation Engine (DLE) for efficient risk management.

  • Cross-chain compatibility via secure messaging protocols.

  • Optimized gas usage through innovative design patterns.

  • OEV (Optimal 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!

Brand Assets

Curvance brand assets can be found here.

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 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. Oracle Manipulation Risk

The Curvance platform depends on oracles for accurate pricing data. While the dual-oracle system is designed to prevent manipulation, edge cases could occur where both oracles are compromised. Users should be aware of this risk when interacting with the platform.

List of Delegable Actions

Once a user has approved your contract as a delegate, your application can perform various actions on their behalf.

Functions with the post-fix "for" enable delegable actions.

Below is a list of contracts and their delegable actions:

Contract
Delegable Actions

UniversalBalance

depositFor(), withdrawFor(), transferFor(), multiDepositFor(), multiWithdrawFor()

PositionManagement

leverageFor(), deleverageFor()

CToken

depositAsCollateralFor(), redeemCollateralFor(), redeemFor(), borrowFor()

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 WETH/wstETH LP tokens, which earns an APR of approximately 4%. Leveraging the ERC-4626 architecture, Curvance directs the LP tokens to an underlying protocol to capture yield. With an 80% collateralization ratio, the user can borrow up to $80 in debt against their LP tokens.

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.

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.


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.

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.

Auction Enabled Liquidations

More information coming soon!

Plugin System

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

Borrow

When you borrow USDC through Curvance, you're creating a debt position against your collateral. The system constantly evaluates your position to ensure it remains healthy (above the required collateralization ratio). The borrowing process is straightforward - you simply specify how much USDC you want to borrow, and the cUSDC contract handles the debt issuance. The borrowed USDC is sent directly to the receiver wallet.

View functions inside provide handy data needed to calculate borrow amounts.

To borrow, you may call the borrow() function in the respective cToken contract using these function arguments:

Type
Name
Description

Implementation snippet:

Your borrowing capacity depends on your collateral value, the collateralization ratio of your assets, and current market conditions. Curvance's risk model determines the maximum amount you can borrow against your collateral.

Error Handling

When interacting with cUSDC contracts, you might encounter various errors. Curvance uses error selectors to provide specific information about what went wrong:

Common error scenarios include:

  • Insufficient collateral for your requested borrow amount.

  • Market paused for borrowing (temporary protocol safety measure).

  • Trying to borrow less than the minimum loan size.

  • Transaction would result in an unhealthy position.

Integration Cookbook

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

  1. More coming soon

To minimize the risk of bad debt and potential loss of funds, deposit user funds exclusively into conservative markets with low-risk profiles.


🅿️ Parking Your Users' Idle Funds in Curvance

Objectives:

  • Safely park user idle funds in Curvance markets.

  • Let your platform retrieve those funds via delegated actions.

One time setup (per cToken market)

The user calls , delegating your platform's address as the delegate.

Optionally: To reduce the total amount of transactions even further, from your app, call approve in the idle asset to allow the Curvance cToken to spend.

Note: Users can mass-revoke all delegates anytime via the .

Depositing idle funds

From your platform: source USDC (e.g., transferred to you by the user) and call .

Note: deposits pull USDC from the caller (msg.sender), not the receiver.

Retrieving funds on demand

Call to redeem the user's cUSDC shares and receive USDC.

Redeeming cTokens does not require ERC20 approval.

Practical tips

  • Use previews (previewDeposit(), previewRedeem()) client-side for UX estimates.

  • Pauses/caps may limit deposits/redemptions; handle errors gracefully.

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


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

  2. Call from your contract, with its address as the recipient.

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

Call if redeeming in shares, or if redeeming in underlying.

Redeeming cTokens does not require ERC20 approval.


Application Specific Sequencing

Application Specific Sequencing in Curvance

The Curvance protocol introduces an oracle-agnostic, MEV-optimized system for handling liquidations, developed in partnership with Atlas. This system integrates orderflow auctions, allowing liquidators to bid for the right to liquidate collateral in the Curvance lending markets. This makes the Curvance platform one of the first to leverage this advanced approach in DeFi.

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.

Weblinks

Official Links

Ecosystem Links

  • /

Plugin System

uint256

amount

The amount of the underlying asset to borrow.

address

recipient

The address where the borrowed tokens will be sent.

async function borrowUSDC(amount) {
  // USDC has 6 decimal places
  const USDCDecimals = await USDC.decimals();
  const amountInUsdcUnits = ethers.utils.parseUnits(amount.toString(), USDCDecimals);
  
  // Borrow USDC
  const borrowTx = await cUSDC.borrow(amountInUsdcUnits);
  const receipt = await borrowTx.wait();
  
  console.log(`Successfully borrowed ${amount} USDC`);
  return receipt;
}
async function safeBorrowUSDC(amount) {
  try {
    return await borrowUSDC(amount);
  } catch (error) {
    console.error('Borrowing failed:');
    
    if (error.data) {
      const errorSelector = error.data.slice(0, 10);
      if (errorSelector === 
        ethers.id('MarketManager__InsufficientCollateral()').slice(2, 10))) 
        console.error('Invalid parameter provided');
    } else {
      console.error('Error details:', error.message);
    }
    throw error;
  }
}
ProtocolReader
Parking Your Users' Idle Funds in Curvance
Include a Curvance Market in Your Vault
cUSDC.setDelegateApproval(<yourPlatformAddress>, true)
approval index
cUSDC.deposit(assets, <userAddress>)
cUSDC.redeemFor(shares, <yourPlatformAddress>, <userAddress>)
cToken.deposit()
cToken.redeem()
cToken.withdraw()
Website
Discord
Twitter
Farcaster
Telegram
Medium
CoinGecko
CMC

Get started integrating Curvance into your platform

Learn about our Lending Protocol

Learn about our leverage system

Learn about our liquidation engine

Learn about our plugin and delegation system

Cover
Cover
Cover
Cover
Cover

Repaying Debt

When you're ready to reduce or eliminate your debt, (using USDC as an example) you can repay it through the cUSDC contract. Repayment requires you to first approve the cUSDC contract to spend your USDC, then call the repay() function with the following function arguments:

Type
Name
Description

uint256

amount

The amount of the underlying asset to repay, or 0 for the full outstanding amount.

Implementation snippet:

async function repayUSDCDebt(amount) {
  const USDCDecimals = await USDC.decimals();
  
  // Use 0 to repay the full outstanding amount
  const isFullRepay = amount === 'full';
  const amountToRepay = isFullRepay ? '0' : amount;
  const amountInSmallestUnit = ethers.utils.parseUnits(amountToRepay, USDCDecimals);
  
  // Approve USDC to be spent by eUSDC contract
  let approvalAmount = amountInSmallestUnit;
  const timestamp = block.timestamp;
  if (isFullRepay) {
    // Use the ProtocolReader contract to get a buffered amount that includes
    // any interest that needs to be accrued without making a separate transaction.
    const block = await provider.getBlock('latest');
    approvalAmount = protocolReader.debtBalanceAtTimestamp(
      userAddress,
      cUSDCAddress,
      timestamp);
  }
  
  await USDC.approve(cUSDCAddress, approvalAmount);
  
  // Repay the debt
  const repayTx = await cUSDC.repay(amountInSmallestUnit);
  const receipt = await repayTx.wait();
  
  console.log(`Repaid ${isFullRepay ? 'full debt' : amount + ' USDC'}`);
  return receipt;
}

Curvance simplifies full repayment by allowing you to input 0 as the amount, which automatically clears your entire outstanding debt, including any accrued interest. This removes the need for manual calculations of the total debt amount.

Repaying Debt on Behalf of Others

A unique feature of Curvance is the ability to repay another users debt. This can be useful in various scenarios, such as:

  • Helping a friend avoid liquidation.

  • Managing multiple wallets in a DAO or organization.

  • Implementing complex DeFi strategies.

The process is similar to regular repayment but uses the repayFor function using the following arguments:

Type
Name
Description

address

account

The account address to repay on behalf of.

uint256

amount

The amount to repay, or 0 for the full outstanding amount.

Implementation snippets:

async function repayForAccount(account, amount) {
  const USDCDecimals = await USDC.decimals();
  const amountInSmallestUnit = ethers.utils.parseUnits(amount.toString(), USDCDecimals);
  
  // Approve USDC to be spent by cUSDC contract
  await USDC.approve(eUSDCAddress, amountInSmallestUnit);
  
  // Repay debt on behalf of another account
  const repayTx = await eUSDC.repayFor(account, amountInSmallestUnit);
  const receipt = await repayTx.wait();
  
  console.log(`Repaid ${amount} USDC on behalf of ${account}`);
  return receipt;
}

This function allows anyone to repay debt for any user without requiring permission from the borrower, creating interesting possibilities for social coordination in DeFi.

Collateral and Debt Caps

Minimizing systemic risk

Collateral and Debt Caps in Curvance

Collateral and debt caps are 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 determined by a third-party risk management group elected by Curvance DAO participants known as the Curvance Collective. These caps are based on an asset’s on-chain liquidity across various pairs within the network. The elected third party also evaluates offside liquidity (liquidity distributed across asset pairs within the protocol) and sets caps to ensure stability.

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, earning native gauge emissions. 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 Collective (a third‑party risk group elected by Curvance DAO), using on-chain liquidity depth, venue fragmentation, expected unwind velocity, asset volatility, and historical liquidity resiliency. Focus on borrow‑side absorbable liquidity across major routes, time‑to-unwind assumptions, and keeper/liquidator capacity. Caps can differ across isolated markets for the same asset, reflecting venue-specific liquidity.

Debt Cap Example

Behavior at Cap

  • New borrows: Revert once the market reaches its debt cap.

  • Repayments and liquidations: Always allowed.

  • Interest: 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 carefully linking collateral and debt caps to liquidity dynamics and adjusting them via DAO governance, the protocol provides a robust, stable approach to collateralized borrowing. This dual‑cap method aligns user security with broader protocol health, ensuring controlled exposure on both sides of the balance sheet and allowing users to scale responsibly within DeFi.

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.

Oracles

Pricing assets inside Curvance

Dual Oracle System and Circuit Breaker Protection

To enhance security, the protocol primarily uses a Dual Oracle system, leveraging data from various sources such as Redstone, Chainsight, Pyth, Chainlink, API3, Chronicle, and more. While most assets utilize two independent oracle sources to ensure accurate pricing and protect against manipulation and volatility, the protocol can support assets with a single oracle when appropriate. For instance, assets like wstETH, which are redeemable for stETH, may not require a second oracle due to their inherent price stability and transparency.

How the Dual Oracle System Works

If the price data from both oracles diverges significantly—due to either manipulation or extreme market fluctuations— The Curvance protocol can pause borrowing and redemptions for that asset. Preset parameters trigger this pause, allowing time for Oracle prices to stabilize and converge.

  • In the event of an abnormal pricing discrepancy between oracle feeds, typically seen during flash loan or oracle attacks, the creation of new debt and redemptions are halted while liquidations are still allowed to be processed.

  • If an extreme discrepancy is detected, the creation of new debt, redemptions, and liquidations are all halted.

Together, these measures form the protocol's Circuit Breaker System, which safeguards users during market anomalies.

Lender Protection and Price Favorability

To provide additional security for lenders, the dual oracle system uses the most favorable oracle-reported price when calculating the final asset price in user liquidity checks, optimizing protection against bad debt.

Example:

  • If one oracle reports an asset price of $100 while another reports $101, the collateral value is set at $100 for borrowing calculations, ensuring conservative collateral valuation and protecting borrowers from taking more debt than they should.

  • If a user risks liquidation and stablecoin oracle prices differ, e.g., $1 and $1.01, the system will evaluate the position at the higher $1.01 price, providing added security for lenders.

Freshness Validation

Curvance enforces per‑asset heartbeat checks to ensure timely price updates. Stale or missing feeds are ignored; prolonged staleness or large cross‑oracle divergence 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:

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

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

Deleveraging

Understanding Deleveraging

Deleveraging is the process of reducing your position's leverage by withdrawing collateral and repaying debt. This can be done in two ways:

  1. Partial deleveraging: Reduce leverage while maintaining an active position.

  2. Full deleveraging: Completely unwind the position by repaying all debt.

Preparing for Deleveraging

Before deleveraging, you need to:

  1. Understand your current position: Review your collateral amount, debt amount, and health factor.

  2. Set an appropriate slippage tolerance: Typically 0.5-2% depending on asset volatility.

  3. Calculate the optimal deleveraging amounts: Determine how much collateral to withdraw and debt to repay.

Constructing the DeleverageAction struct

Deleveraging requires constructing a DeleverageAction struct that specifies how to unwind your position:

Executing the Deleverage Operation

With the DeleverageAction struct prepared, execute the deleverage operation:

Important Considerations

  1. Swap Data Configuration:

    1. The swapData array can contain multiple swaps for complex routes.

    2. Each swap must specify the correct input/output tokens and amounts.

    3. Slippage tolerance should be set appropriately for each swap.

  2. Amount Calculations:

    1. collateralAmount: The amount of position tokens to withdraw.

    2. repayAmount: The amount of debt to repay in the borrowed asset.

    3. Ensure these amounts are properly calculated to maintain desired position size.

  3. Protocol-Specific Features:

    1. Some protocols may require additional data in the auxData field.

    2. Check protocol documentation for specific requirements.

    3. Consider using protocol-specific optimizations when available.

Withdraw

Querying Redeem Amounts

If you would like your platform to display the amount of underlying tokens the user would receive for redeeming cTokens, you may use the convertToAsset() function present in all cToken contracts using the following arguments:

Type
Name
Description

Conversely, if you want to let the user choose how much underlying assets to withdraw, you may use the convertToShares() function:

Type
Name
Description

Withdraw from cTokens (cUSDC example)

To withdraw USDC from cUSDC directly, you may use the withdraw(assets) or redeem(shares) functions present in all cToken contracts using the following arguments:

Withdrawing/Redeeming does not force collateral removal.

Type
Name
Description

Below is an example of using withdraw():

Below is an implementation of using redeem(), letting the user choose how much underlying to redeem:

Using redeemFor

If your app requires you to withdraw on users' behalf, you can use the redeemFor() function in the cToken contract using the following function arguments:

Make sure your platforms' address is approved as a delegate. Visit for more information.

Type
Parameter
Description

Withdrawing Collateral

When withdrawing from collateralized cTokens, you have a few choices of functions to call depending on the needs of your platform.

  • redeemCollateral() - If you would like to redeem cTokens that are currently being used as collateral.

  • redeemCollateralFor() - If you would like to redeem cTokens that are currently being used as collateral, on behalf of a user. Requires the user to delegate your platform.

ERC20 approval is not required for redeeming cTokens.

Removing collateral should be done with extreme caution. Removing collateral can increase the user's loan to value ratio, which increases the risk of liquidation.

Collateralize

Depositing WMON as Collateral

When depositing into the WMONCToken as collateral, you're also providing liquidity for lending if lending is enabled on the specific cToken.

First, check the user's balance and ensure they have enough MON:

Then approve and make the deposit by calling deposit() in the cToken contract, or depositAsCollateral() to both deposit and post as collateral all in one transaction.

Function arguments when:

calling deposit():

Type
Name
Description

calling depositAsCollateral():

Type
Name
Description

Alternatively, if your app requires users depositing for another address, you can use depositAsCollateralFor(). See our quickstart guide for more information.

Loans & Collateral

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.

The MarketManager coordinates all cTokens, for a deep dive check out this article:

Setting Up Your Integration

First, install ethers.js, for the examples in these docs, we use ethers 5.7.x.

You'll need to define your contract addresses and ABIs. Here's a simplified example for the most important ones:

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 cToken is not paused:

Call with the following arguments. Returning a tuple of booleans, indicating if minting, collateralization, and borrowing is paused.

Type
Description

Implementation:

  1. Collateral Caps: If depositing as collateral, check if the asset has reached its collateral cap by calling collateralCaps(), and marketCollateralPosted() in the MarketManager contract:

Calling collateralCaps() using these function arguments, returning uint256 indicating the maximum amounts of cTokens that can be posted as collateral:

Type
Description

Call marketCollateralPosted() which returns a uint256 indicating the current amount of cTokens currently posted as collateral:

Type
Description

Implementation:

  1. Minimum Hold Period: Be aware that Curvance implements a 20-minute minimum hold period for collateralized deposits to mitigate flash loan attacks.

  2. Non Rebasing: 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.

Lend Assets

For a detailed explanation of BorrowableCTokens, check out this article:

Depositing USDC for Lending

To deposit USDC into cUSDC directly, you may use the deposit() function present in all cToken contracts using the following arguments:

Type
Name
Instruction

Below is a full implementation:

When you deposit USDC into cUSDC:

  1. Your USDC tokens are transferred to the cToken contract.

  2. You receive cUSDC tokens representing your lending position.

  3. Your USDC becomes available for borrowers to borrow (subject to their collateral).

  4. As borrowers pay interest, the exchange rate between cUSDC and USDC increases.

  5. When you redeem your cUSDC tokens later, you receive your original USDC plus accrued interest.

// Define how much collateral to withdraw
const collateralAmount = ethers.utils.parseUnits('300', 18);
const repayAmount = ethers.utils.parseUnits('200', 18); // Amount of debt to repay

// Create swap data to convert collateral to borrow asset
// Note: This is an array of swaps, allowing for multi-hop routes
const swapData = [{
  target: '0x1111111254EEB25477B68fb85Ed929f73A960582', // 1inch router
  inputToken: underlyingAsset,
  outputToken: borrowUnderlying,
  inputAmount: ethers.utils.parseUnits('250', 18), // Amount needed to convert to repay debt
  call: '0x...', // Encoded swap call data
}];

// Construct deleverage struct
const deleverageAction = {
  cToken: COLLATERAL_CTOKEN,
  collateralAssets: collateralAmount,
  borrowedToken: BORROWED_CTOKEN,
  swapData: swapData,
  repayAmount: repayAmount,
  auxData: '0x' // Optional for specialized protocols
};
// Set slippage tolerance (1%)
const slippage = ethers.utils.parseUnits('0.01', 18);

// Execute the deleverage transaction
const tx = await positionManagement.deleverage(
  deleverageData, 
  slippage,
  2000000
);

const receipt = await tx.wait();
console.log(`Position deleveraged! Tx hash: ${receipt.transactionHash}`);

uint256

tokens

The number of cToken shares to theoretically convert to underlying assets

uint256

amount

The number of underlying tokens to theoretically convert to eTokens

uint256

assets (if withdraw) shares (if redeem)

The number of assets/shares to withdraw/redeem.

address

recipient

The account who will receive the underlying assets

// Get the cUSDC contract with signer
const cUSDC = new ethers.Contract(ADDRESSES.EUSDC, ETOKEN_ABI, signer);

// Get the amount of shares to redeem from the user's input amount
// userInputAmount being equal to "1000", representing 1000 USDC
// which should be formatted to 1000 * 10^6
var USDCWithdrawAmountFormatted = ethers.utils.parseUnits(userInputAmount, 6);

// Convert the underlying amount to shares
const cUSDCSharesToRedeem = await cUSDC.convertToShares(USDCWithdrawAmountFormatted);

// Redeem cUSDC for USDC
const underlyingRedeemed = await cUSDC.redeem(depositAmount, recipientAddress);
// Get the cUSDC contract with signer
const cUSDC = new ethers.Contract(ADDRESSES.CUSDC, CTOKEN_ABI, signer);

// Get the amount of shares to redeem from the user's input amount
// userInputAmount being equal to "1000", representing 1000 USDC
// which should be formatted to 1000 * 10^6
var USDCWithdrawAmountFormatted = ethers.utils.parseUnits(userInputAmount, 6);

// Convert the underlying amount to shares
const cUSDCSharesToRedeem = await cUSDC.convertToShares(USDCWithdrawAmountFormatted);

// Redeem cUSDC for USDC
const underlyingRedeemed = await cUSDC.redeem(depositAmount);

uint256

assets (if withdrawFor) shares (if redeemFor)

The number of assets/shares to withdraw/redeem.

address

recipient

The account that should receive the underlying assets.

Plugin Integration
const WMON = new ethers.Contract(ADDRESSES.WMON, ERC20_ABI, provider);
const balance = await WMON.balanceOf(userAddress);
const depositAmount = ethers.utils.parseEther("10"); // 10 WMON tokens

if (balance.lt(depositAmount)) {
  throw new Error("Insufficient WMON token balance");
}

uint256

assets

The amount of underlying assets to deposit.

address

receiver

The account that should receive the pToken shares.

uint256

assets

The amount of underlying assets to deposit.

uint256

receiver

The account that should receive the pToken shares.

// Approve the cToken to spend WMON
await WMON.approve(ADDRESSES.WMON_CTOKEN, depositAmount);

// Get the cToken contract
const cToken = new ethers.Contract(
  ADDRESSES.WMON_CTOKEN,
  CTOKEN_ABI,
  signer
);

// Deposit as collateral or regular deposit
if (asCollateral) {
  await cToken.depositAsCollateral(depositAmount);
} else {
  await cToken.deposit(depositAmount, userAddress);
}
Plugin Integration
npm install [email protected]
const ADDRESSES = {
  SHMON: "0x21E27a5E5513D6e65C4f830167390997aA84843a",
  USDC: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  SHMON_CTOKEN: "0x..." // Replace with actual cToken address
  USDC_CTOKEN: "0x..." // Replace with actual cToken address
};

address

Address of the cToken to check the minting status of.

const marketManager = new ethers.Contract(MARKET_MANAGER_ADDRESS, MARKET_MANAGER_ABI, provider);
const actionsPaused = await marketManager.mintPaused(SHMON_CTOKEN);

address

Address of the cToken to check the max amount of shares that can be posted as collateral.

address

Address of the cToken to check the amount of collateral posted.

const cap = await marketManager.collateralCaps(SHMON_CTOKEN);
const posted = await marketManager.marketCollateralPosted(SHMON_CTOKEN)
const capReached = posted.gte(cap) && !cap.isZero();
Market Manager
actionsPaused()

uint256

assets

The amount in assets to deposit.

address

receiver

The address that will receive the cToken shares.

// Get the cUSDC contract with signer
const cUSDC = new ethers.Contract(ADDRESSES.CUSDC, CTOKEN_ABI, signer);
// Get the USDC contract with signer
const usdc = new ethers.Contract(ADDRESSES.USDC, ERC20_ABI, signer);

// Format amount (USDC has 6 decimals)
const depositAmount = ethers.utils.parseUnits("1000", 6); // 1000 USDC

// Approve eUSDC to spend USDC
await usdc.approve(ADDRESSES.CUSDC, depositAmount);

// Deposit USDC for lending
await cUSDC.deposit(depositAmount, userAddress);
BorrowableCTokens

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.

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.

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

For more information on lFactor, check out this page: Dynamic Liquidation Engine.


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.

  • Hard Collateral Requirement: The premium of excess collateral required to avoid triggering a hard liquidation.

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

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.

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.

Non-Auction Liquidations

Step 1: Check if a Position is Liquidatable:

See: Monitoring Position Health.

Step 2: Executing Liquidations

You have two choices of functions to call when executing a liquidation:

liquidate() - Used for maximum liquidations.

Use when:

  • You want to liquidate the maximum allowed amount.

  • You don't know the exact optimal amount.

function liquidate(
    address[] calldata accounts,
    address collateralToken
) external nonReentrant;

liquidateExact() - Used for exact amount liquidations.

Use when:

  • You want to repay an exact amount of debt.

function liquidateExact(
    uint256[] calldata debtAmounts,
    address[] calldata accounts,
    address collateralToken
) external nonReentrant;

Token Flows

What the Liquidator Pays:

Liquidator → Debt Token (underlying) → Borrowable cToken
Amount: result.debtRepaid

What the Liquidator Receives:

Collateral cToken → seize() → Liquidator
Amount: result.liquidatedShares (in cToken shares)

Examples

Executing maximum liquidations

  // Prepare sorted accounts
  const accounts = [
      "0x1111111111111111111111111111111111111111",
      "0x2222222222222222222222222222222222222222",
      "0x3333333333333333333333333333333333333333"
  ];
    // Check liquidation amounts
  const debtAmounts = [0, 0, 0];

  const action = {
      collateralToken: collateralCToken,
      debtToken: debtCToken,
      numAccounts: 3,
      liquidateExact: false,
      liquidatedShares: 0,
      debtRepaid: 0,
      badDebt: 0
  };

  // Approve total debt to repay
  const approveTx = await underlyingDebt.approve(debtCToken, result.debtRepaid);
  await approveTx.wait();

  // Execute batch liquidation
  const liquidateTx = await borrowableCUSDC.liquidate(
      accounts,
      collateralCToken
  );
  await liquidateTx.wait();

  console.log("All three accounts liquidated in one transaction");

Executing Exact Amount Liquidation

  // Liquidate exactly 500 USDC, and store into an array.
  
  const accounts = ["<borrowerAddress>"];
  const debtAmounts = [ethers.utils.parseUnits("500", 6)];
  
  // Get underlying debt token
  const underlyingDebtAddress = await borrowableCUSDC.asset();
  const underlyingDebt = new ethers.Contract(
      underlyingDebtAddress,
      ["function approve(address,uint256) returns (bool)"], // abi
      signer
  );

  // Approve tokens
  const approveTx = await underlyingDebt.approve(debtCToken, debtAmounts[0]);
  await approveTx.wait();
  
  // Execute exact liquidation
  const liquidateTx = await borrowableCUSDC.liquidateExact(
      debtAmounts,
      accounts,
      collateralCToken
  );
  await liquidateTx.wait();

  console.log("Liquidated a single account for an exact amount");

Liquidation Tips

Finding Liquidation Opportunities:

  • Monitor price feeds for significant collateral price drops or debt price increases.

  • Track accounts with debt close to their maxDebt limit (high-leverage positions).

  • Focus on volatile collateral assets during market downturns.

  • Set up event listeners for Borrow events to catch new leveraged positions early.

Using Smart Contracts vs Scripts:

Smart contracts are strongly recommended over off-chain scripts for executing liquidations:

  • Atomic profitability checks - Contract can calculate profit on-chain and revert if unprofitable, saving you from wasting gas on bad liquidations.

  • Flash loan integration - Borrow capital, liquidate, and repay all in one atomic transaction with zero upfront capital.

  • Composability - Can integrate with DEX swaps to instantly convert seized collateral to stablecoins.

A typical smart contract liquidator pattern: check profitability → revert if not profitable → execute liquidation → swap collateral → ensure profit threshold met. This guarantees you never execute an unprofitable liquidation.

Borrowing & Repayment

Introduction to cTokens in Curvance

In Curvance's lending ecosystem, BorrowableCTokens represent debt positions. Each BorrowableCToken corresponds to a specific underlying asset - for example, cUSDC represents borrowed USDC. When you borrow an asset from Curvance, you're interacting with a cToken contract that tracks your debt.

For an in depth explanation of BorrowableCTokens, check out this guide here: BorrowableCTokens

Understanding cUSDC

cUSDC is the debt token representing borrowed USDC in Curvance. When you borrow USDC, you're effectively taking on cUSDC debt. Key characteristics include:

  • Underlying Asset: USDC (USD Coin) stablecoin with 6 decimal places.

  • Interest Accrual: Interest accumulates continuously based on market conditions, increasing your debt over time.

  • Exchange Rate: The relationship between cUSDC and USDC changes as interest accrues.

  • Dynamic Interest: Rates adjust automatically based on utilization ratio in the USDC lending market.

Curvance implements a 20-minute minimum hold period for collateral, which means your collateral must remain in the system for at least 20 minutes. This enhances security by preventing certain types of exploits and flash loan attacks.

Setting Up Your Development Environment

Before interacting with cUSDC, you'll need to set up your environment with ethers.js. You'll need contract ABIs for interaction, but we'll keep it simple here - you can obtain the full ABIs from the Curvance contract repo.

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

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

// You'll need to obtain the complete ABIs from the Curvance contracts repo
const cUSDCAddress = '0x...';        // cUSDC contract address
const USDCAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'; // USDC address

// Initialize contract instances with appropriate ABIs
const cUSDC = new ethers.Contract(eUSDCAddress, BORROWABLECTOKENABI, wallet);
const USDC = new ethers.Contract(USDCAddress, USDCABI, wallet);

Monitoring Your Debt Position

As interest accrues on your debt, it's important to monitor your position regularly. Curvance provides several functions to help you track your debt balance.The debtBalanceAtTimestamp in the ProtocolReader function fetches your current debt balance with the latest interest applied:

async function checkDebtBalance() {
  const debtBalance = 
    await protocolReader.debtBalanceAtTimestamp(wallet.address, blockTimestamp);
  const USDCDecimals = await USDC.decimals();
  
  console.log(`Current USDC debt: ${ethers.utils.formatUnits(debtBalance, USDCDecimals)}`);
  return debtBalance;
}

The debt balance will increase over time as interest accrues. If your debt grows too large relative to your collateral, you risk liquidation. Maintaining a healthy collateralization ratio is essential for using Curvance safely.

Understanding Market Conditions

The interest rate for cUSDC debt depends on market supply and demand. You can check current market conditions to make informed borrowing decisions:

async function checkBorrowableCUSDCMarketInfo() {
  // Get the current exchange rate (how much USDC each unit of eUSDC is worth)
  // Use exchangeRate() or exchangeRate() using a static call to get the most
  // up to date exchange rate including all vesting periods.
  const exchangeRate = await cUSDC.exchangeRate();
  console.log(`Current exchange rate: ${ethers.utils.formatUnits(exchangeRate, 18)}`); // In WAD format
  
  // Get total outstanding borrows in the market
  const totalBorrows = await cUSDC.callStatic.marketOutstandingDebt();
  const USDCDecimals = await USDC.decimals();
  console.log(`Total USDC borrowed: ${ethers.utils.formatUnits(totalBorrows, USDCDecimals)}`);
  
  return { exchangeRate, totalBorrows };
}

Higher utilization (more borrowing relative to available supply) generally leads to higher interest rates. Monitoring these metrics can help you anticipate changes in borrowing costs.

Risk Considerations

When borrowing through cUSDC, be aware of these risks:

  • Liquidation Risk: If your collateral value falls or your debt increases (through interest accrual), you may face liquidation if your position drops below the required collateralization ratio.

  • Interest Rate Volatility: Rates can change based on market conditions, potentially increasing your debt faster than anticipated.

  • Protocol Risk: Smart contract vulnerabilities or governance decisions could affect your borrowing position.

  • Market Risk: The value of your collateral might decrease relative to your borrowed USDC.

To mitigate these risks, consider maintaining a higher collateralization ratio than the minimum required, and regularly monitor your position's health.

Plugin & Delegation System

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.

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:

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.


Bug Bounty and Audits

Security at Curvance

The Curvance protocol is engineered with security as its number one priority. In this space, where people transact valuable assets, safety is crucial, especially in the case of Curvance, which deals with sophisticated and complex assets.

DeFi can be intimidating; in 2023 alone, close to $2 billion has been exploited through smart contract vulnerabilities. The industry is prone to various attack vectors, and Curvance has worked extremely hard to minimize these risks.

During the creation of the platform, Curvance maintained a strict policy on code review, testing, and security by working with reputable auditors and security experts.

Bug Bounty

Scope: Issues that could result in significant financial loss, critical bugs such as broken liveness conditions, or any flaw that could cause irreversible loss of funds.

Eligibility:

  • You must be the first to report the vulnerability.

  • If an action has been taken you must be able to verify a signature from the same address.

  • You must provide sufficient information for us to reproduce and understand the issue.

Disclosure policy: Please report potential security issues to us as soon as possible after discovery. Allow a reasonable period for us to investigate and address the issue before making any public or third-party disclosures.

Exclusions:

  • Vulnerabilities already known to the team.

  • Issues related to "drunk admin" behavior.

  • Front-end issues that do not lead to smart contract vulnerabilities.

  • The smart contract must be deployed and currently in use.

Bug Bounty Payout

Likelihood ↓ | Severity →
Low
Medium
High

Will happen

$10,000

$50,000

$250,000

May happen

$1,000

$10,000

$50,000

Unlikely to happen

$200

$1,000

$5,000

Audits

Several audits have been made public in the Curvance Public Github Repository.

Auditors

To ensure the highest level of security, Curvance has partnered with several of the leading Web3 security firms and organizations. Each brings their own merit and strengths to the table.

TrustSec (link)

TrustSec serves as the primary security partner, addressing concerns related to potential bugs, exploit vulnerabilities, and overall functionality. They have significantly contributed to the majority of hours invested in code auditing over the past five months. Auditors include Trust, Zach Obront, MiloTruck, and Bernd.

Trail of Bits (link)

Trail of Bits played an important role in creating a highly sophisticated test suite for the complex and extensive code base for the cross-chain money market. ToB helped employ stateful fuzzing and systematically tested code through various actions and states.

Sherlock (link)

Sherlock conducted an independent audit focused on Curvance’s mainnet-specific implementations. Their review covered MEV architecture, transaction ordering, and a comprehensive assessment of mainnet smart contracts prior to deployment.

yAudit (link)

From the yAcademy hosted by Yearn Finance, it spawned yAudit, a team of Web3 hackers and engineers. The team assisted in test expansion and helped with nuanced intricacies, such as external integrations through 4626 vaults.

Public Audit

A public audit has been conducted through Cantina, a groundbreaking marketplace for web3 security. The platform aims to simplify audits and provide tailored experiences with varied pricing.

Cantina connects organizations with security needs to expert auditors (teams and individuals) through Guilds, emphasizing accessibility and credibility. The platform ensures transparency, addressing the challenges faced by solo auditors and smaller audit teams.

Curvance strategically chose Cantina for its audit, recognizing the valuable advantages offered by Cantina's broad audit community and its connection to Spearbit DAO. By tapping into this diverse pool of auditors, Curvance ensures a thorough evaluation of its security protocols, benefitting from varied expertise and specialized knowledge.

This approach aligns with Curvance's commitment to a comprehensive security assessment, leveraging the efficiency and timeliness inherent in a larger audit community.

Liquidations

Overview

Liquidations are a critical mechanism in Curvance that maintain protocol solvency by allowing third parties to repay undercollateralized debt positions in exchange for the borrower's collateral at a discount. When a borrower's collateral value falls below required thresholds, liquidators can step in to close these risky positions before they accumulate bad debt.

How Liquidations Work

Users can borrow assets against their posted collateral. Each collateral asset has specific requirements that determine when a position becomes liquidatable:

  1. Collateral Requirements - Each token has two thresholds:

    • Soft Requirement - Position becomes partially liquidatable.

    • Hard Requirement - Position can be fully liquidated.

  2. Health Factor (lFactor) - Determines liquidation severity:

    • lFactor = 0 - Position is healthy (not liquidatable).

    • 0 < lFactor < 1 - Soft liquidation (partial).

    • lFactor = 1 - Hard liquidation (up to 100%).

  3. Liquidation Incentive - Liquidators receive a bonus on the collateral they seize, compensating them for the service of maintaining protocol health.

When Positions Become Liquidatable

A position becomes liquidatable when:

  • The value of collateral falls relative to the debt

  • The price of borrowed assets increases

  • Interest accrues on the debt, pushing the position over the threshold

  • Any combination of the above factors

Two Types of Liquidations

Non-Auction Liquidations

Standard liquidations available to anyone at any time. These use protocol-defined parameters (close factor, liquidation incentive) based on the position's health factor.

Auction-Enabled Liquidations

Special liquidations triggered by authorized auction managers with custom parameters. These provide auction participants priority access via an "auction buffer" that effectively gives them better prices than regular liquidators.

Feature
Non-Auction
Auction-Enabled

Liquidator Workflow

  1. Discover accounts - Monitor events or query on-chain data to find positions

  2. Validate liquidation - Call canLiquidate() to get exact amounts and profitability.

  3. Execute - Call liquidate() or liquidateExact() on the debt token.

  4. Receive collateral - Seized collateral is transferred to your address.

Key considerations:

  • Account Discovery - Use event monitoring (PositionUpdated, Borrow, CollateralUpdated etc.).

  • Profitability - Always check profitability before executing, consider gas costs.

  • Smart Contracts - Highly recommended over scripts for atomic profitability checks.

  • Flash Loans - Can be used to liquidate without upfront capital.

  • Competition - Popular liquidation opportunities can be competitive, optimize for speed and gas efficiency.

Position Management

Overview

The Position Management system is a core component of Curvance's lending protocol, enabling advanced leverage and deleverage operations for user positions. It provides a structured way for users to manage complex DeFi positions across multiple asset types while maintaining protocol safety.

Architecture Design

Core Components

PositionManagementBase

The abstract base contract provides the core functionality for all position management implementations. It includes:

  • Common state variables and constants.

  • Core leverage/deleverage logic.

  • Safety checks and fee calculations.

  • Market status queries and calculations.

  • Standard integration points with other protocol components.

Specialized Implementations

Each implementation extends the base functionality to support specific asset types:

  • PositionManagementSimple: Basic implementation for standard tokens with simple swap requirements.

  • PositionManagementPendlePT: Specialized for Pendle Principal Tokens, handling their unique yield token mechanics.

  • PositionManagementPendleLP: Manages positions for Pendle liquidity provider tokens.

  • PositionManagementVelodrome: Handles Velodrome AMM-specific operations..

  • PositionManagementAerodrome: Extends Velodrome implementation for Aerodrome protocol

Key Data Structures

LeverageStruct

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

Contains data for deleverage operations, specifying which collateral to liquidate and how to route funds to repay debt.

Integration Points

The Position Management system interacts with multiple components of the Curvance ecosystem:

  • MarketManager: For checking account status, debt limits, and collateral values.

  • CentralRegistry: For protocol-wide configuration and permissions.

  • CTokens: Position tokens that serve as collateral.

  • BorrowableCTokens: Debt tokens that users borrow from.

  • SwapperLib: For executing token swaps during leverage/deleverage operations.

  • External Protocols: Direct integrations with Pendle, Velodrome, and other DeFi protocols.

Security Features

The Position Management architecture implements several security features:

  • Slippage Protection: Ensures executed trades don't lose value beyond user-specified thresholds.

  • Sanity Checks: Validates all operations against user account status and market constraints.

  • Maximum Leverage Limits: Prevents users from taking on excessive risk.

  • Fee Calculations: Ensures protocol takes appropriate fees for providing leverage services.

  • Reentrancy Protection: Guards against reentrancy attacks during complex operations.

Protocol Interactions

During position management operations, the system follows these general steps:

  • Validation: Check that the requested operation is valid for the user's account.

  • Calculation: Determine limits, risks, and required amounts.

  • Execution: Perform necessary token transfers, borrows, or repayments.

  • Swapping: Convert between token types as needed using appropriate swap routes.

  • Position Update: Update the user's collateral and debt positions.

  • Fee Handling: Calculate and collect protocol fees.

This architecture enables Curvance to support complex leverage strategies across diverse asset types while maintaining protocol security and risk parameters.

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:

Type
Name
Description

Events:


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:

Type
Name
Description

Return data:

Type
Description

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:

Type
Name
Description

Events:

Leveraging

1. Initial Deposit and Leverage

The most common approach is to deposit and leverage in a single transaction. This is ideal for users who want to create a leveraged position from scratch.

Preparing for Leverage

Before leveraging, you need to:

  1. Understand your target position: Determine the collateral asset, borrow asset, and desired leverage ratio.

  2. Set an appropriate slippage tolerance: Typically 0.5-2% depending on asset volatility.

  3. Approve the necessary contracts: Your collateral token must approve the Position Management contract.

Here's how to prepare the deposit and leverage transaction:

Constructing the LeverageAction

The key to a successful leverage operation is properly constructing the LeverageStruct:

Executing the Leverage Operation

With the LeverageStruct prepared, execute the leverage operation:

2. Leveraging an Existing Position

If you already have collateral deposited, you can increase your leverage without an additional deposit:

Important Considerations

  1. Position Health and Risk Management:

    1. Monitor your position's health factor before and after leveraging.

    2. Consider the maximum leverage ratio allowed by the protocol.

    3. Be aware of liquidation risks when increasing leverage.

    4. Calculate the minimum collateral required to maintain a safe position.

  2. Swap Data Configuration:

    1. The swapData must accurately reflect the desired swap route.

    2. Ensure the inputAmount matches the borrowAmount in the leverage struct.

    3. Set appropriate slippage tolerance for the swap (typically 0.3-1% for stable pairs, 1-3% for volatile pairs).

  3. Amount Calculations:

    1. Ensure these amounts are properly calculated to achieve desired leverage ratio.

    2. Consider protocol fees when calculating amounts.

  4. Protocol-Specific Features:

    1. Some protocols may require additional data in the auxData field.

    2. Check protocol documentation for specific requirements.

Who can execute

Anyone

Anyone, but processed by auction-permissioned accounts via account abstraction.

When available

Always

When market is unlocked for auction

Parameters

Protocol-defined

Can be custom

Priority buffer

None

10 or 50 BPS discount on collateral

function setCooldown(uint256 cooldown) external

uint256

cooldown

The length of time (in seconds) that transferability should remain restricted after a lock is disabled. Max value is 52 weeks.

// Defined in ActionRegistry.sol
event CooldownSet(address indexed user, uint256 userLockCooldown);
function checkTransfersDisabled(address user) external view returns (bool)

address

user

The address to check transfer status for.

bool

Returns true if the user has transfers disabled or if their cooldown period has not yet expired.

function setTransferLockStatus(bool transferDisabled) external

bool

transferDisabled

true to lock transfers (disable transferability), false to unlock transfers (enable transferability after cooldown).

// Defined in ActionRegistry.sol
event LockStatusChanged(
    address indexed user,
    bool isLocked,
    uint256 transferEnabledTimestamp
);
// Approve the position management contract to spend your tokens if depositing and leveraging in one transaction
const underlyingContract = new ethers.Contract(
  underlyingAsset,
  UNDERLYING_ABI,
  signer
);
const depositAmount = ethers.utils.parseUnits('1000', 18); // Adjust amount and decimals
await underlyingContract.approve(POSITION_MANAGEMENT, depositAmount);
// Create the swap data structure
// This example uses 1inch swap data, but any DEX can be used
const swapData = {
  target: '0x1111111254EEB25477B68fb85Ed929f73A960582', // 1inch router
  inputToken: COLLATERAL_ASSET_UNDERLYING,
  outputToken: BORROWED_ASSET_ADDRESS,
  inputAmount: ethers.utils.parseUnits('500', 18), // Amount to borrow and swap
  call: '0x...', // Encoded swap call data from 1inch API
};

// Construct the LeverageAction struct
const leverageAction = {
  borrowableCToken: BORROWABLECTOKEN,
  borrowAssets: ethers.utils.parseUnits('500', 18),
  cToken: CTOKEN,
  expectedShares: ethers.utils.parseUnits('0.25', 18), // expected amount of collateral cTokens
  swapData: swapData,
  auxData: '0x' // Optional auxiliary data for specialized protocols
};
// Set slippage tolerance (1%)
const slippage = ethers.utils.parseUnits('0.01', 18);

// Execute deposit and leverage in one transaction
const tx = await positionManagement.depositAndLeverage(
  depositAmount,
  leverageData,
  slippage,
  2000000
);

const receipt = await tx.wait();
console.log(`Leveraged position created! Tx hash: ${receipt.transactionHash}`);
const tx = await positionManagement.leverage(
  leverageData, // Same structure as above
  slippage,
  2000000
);

const receipt = await tx.wait();
console.log(`Position further leveraged! Tx hash: ${receipt.transactionHash}`);

Glossary

Term
Description

Application Specific Sequencing

A method for ordering transactions within an application to optimize functionality and achieve a specific use case.

Bad Debt

Occurs when a user's collateral value falls below the amount required to cover a user's outstanding loans.

Bribe/Incentive

Refers to incentivizing governance participants, such as veCVE holders, to vote in favor of specific gauge emissions that benefit the briber.

Close Factor (cFactor)

The % amount of a debt position that can be "closed" (repaid) on liquidation. This value scales between the "Base" rate (configured by Curvance Collective) up to 100% on a hard liquidation.

Collateral

An asset pledged by a borrower to secure a loan. If the borrower defaults, the collateral can be used to recover the owed debt.

Collateral Caps

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.

Collateral Requirement

The premium of excess collateral required to avoid triggering a soft or hard liquidation. Part of the formula in the Curvance Dynamic Liquidation Engine.

Collateralization Ratio

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.

Curvance Collective

Refers to active veCVE holders who participate in Curvance DAO governance.

CVE

The Curvance protocol's native token, which can be locked into veCVE.

Dual Oracle System

A key component of the Curvance platform’s infrastructure which compares two separate oracle price feeds for each asset, ensuring accurate pricing and safeguarding against manipulation and volatility.

Dynamic Interest Rates

Refers to the Curvance platform's interest rates, which adjust in real-time based on market demand within each lending pool.

Emergency Unlock

The only way to unlock veCVE early, subject to a penalty that redistributes forfeited tokens to the DAO treasury.

ERC-4337

ERC-4337 is a standard for smart accounts that enables account abstraction. This standard enhances application-specific sequencing for liquidation auctions and OEV capture.

ERC-4626

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.

Borrowable cTokens

When users deposit assets into Curvance as lenders, they receive a proportionate amount of Borrowable cTokens, representing their share in the lending pool.

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.

Health Factor

A numerical representation of the safety of a user's collateralized position. It measures how close a position is to being liquidated.

Interest

The fee borrowers pay to lenders for accessing funds

Liquidation Factor (lFactor)

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.

Liquidation Fee

The penalty the protocol takes from a user's collateral during a liquidation. Part of the formula in the Curvance Dynamic Liquidation Engine.

Liquidation Incentive

The liquidator's incentive to liquidate a user position. Part of the formula in the Curvance Dynamic Liquidation Engine.

Loan to Value (LTV)

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.

Looping

A strategy in which a user deposits a collateral asset and borrows funds to purchase more of an asset.

Multichain Fee Distribution

Curvance's pro-rata distribution of fees to veCVE holders, allowing them to share in protocol revenue generated across all chains.

Multichain Gauge

Curvance's governance model enabling veCVE holders to vote on distribution of native incentives to any supported pool on any chain, addressing the prevalent issue of siloed governance in traditional gauge systems.

Multichain Lock Migration

The ability for users to transfer their veCVE across supported chains at any time during the lock period.

Oracle

A service that provides smart contracts with access to external data such as price feeds of assets.

Orderflow Auctions

A decentralized offchain auction system for "selling" the opportunity to liquidate user positions inside the Curvance Protocol.

cToken

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.

Socalized Bad Debt

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.

Soft Liquidation

A partial liquidation occurs with a small penalty, preserving more of the user's collateral compared to traditional full liquidation designs.

Utilization Rate

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.

Vault

Where users can deposit assets to benefit from auto-compounding, ecosystem/partner incentives, and Curvance's native gauge emissions. Additionally, users can collateralize their positions within these vaults.

veCVE

Vote-escrowed CVE (veCVE) enables users to participate in DAO voting, receive platform revenue, and direct native gauge emissions.

Wormhole Standard Relayer

A decentralized crosschain transaction execution service. Allows users to move tokens from one chain to another once their desired transaction instructions is processed by the Wormhole Guardian Network.

Wormhole Queries

A decentralized data querying service. Consults the Wormhole Guardian Network to validate data points across many different chains simultaneously.

Flash Loans

Overview

Curvance provides flash loan functionality through the BorrowableCToken contract, allowing developers to borrow assets without collateral for the duration of a single transaction. This guide walks you through implementing flash loans in your smart contracts.

Key Concepts

What is a Flash Loan?

A flash loan is an uncollateralized loan that must be borrowed and repaid within a single transaction. If the loan is not repaid (plus fees) by the end of the transaction, the entire transaction reverts.

Flash Loan Fee

  • Fee Rate: 4 basis points (0.04%)

  • Calculation: fee = (assets * 4) / 10000

  • The fee is added to the total assets in the market after the flash loan completes

Implementation Steps

Step 1: Implement the IFlashLoan Interface

Your contract must implement the onFlashloan() function to receive flash loan callbacks.

function onFlashLoan(
    uint256 assets,
    uint256 assetsReturned,
    bytes calldata data
) external returns (bytes32) {
    // Verify the callback is from the expected BorrowableCToken
    require(msg.sender == borrowableCToken, "Unauthorized callback");

    // Your custom logic here.
    // At this point, you have received 'assets' amount of tokens.

    // IMPORTANT: You must approve the BorrowableCToken to pull back
    // the loan amount + fee before this function returns.
    SafeTransferLib.safeApprove(asset, borrowableCToken, assetsReturned);

    // Return value (not currently checked by the BorrowableCToken contract).
    return keccak256("ERC3156FlashBorrower.onFlashLoan");
}

Step 2: Initiate the Flash Loan

Call the flashLoan() function on the BorrowableCToken contract.

function myFlashloanFunction() external {
    // Prepare any data you want to pass to the callback
    bytes memory data = abi.encode(msg.sender, someData);

    // Execute the flash loan
    // This will:
    // 1. Transfer 'amount' tokens to this contract
    // 2. Call onFlashLoan() on this contract
    // 3. Pull back 'amount + fee' tokens from this contract
    IBorrowableCToken(borrowableCToken).flashLoan(amount, data);
}

Flash Loan Execution Flow

  1. Your contract calls flashLoan(assets, data) .

  2. Fee is calculated fee = (assets * FLASHLOAN_FEE) / BPS.

  3. Assets are transferred to your contract.

  4. Your callback is executed.

  5. Assets + fee are pulled back into BorrowableCToken.

  6. Event Flashloan(assets, fee, msg.sender) is emitted.

Important Considerations

1. Available liquidity

Before initiating a flash loan, ensure sufficient assets are available:

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

2. Fee Calculation

Always calculate the exact fee you'll need to repay:

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

3. Token Approval

Your contract must approve the BorrowableCToken to pull back the loan + fee:

IERC20.usdc.approve(borrowableCToken, assetsReturned);

If you fail to approve or don't have sufficient balance, the transaction will revert.

Error Handling

Error
Cause
Solution

BorrowableCToken__ZeroAmount

Requested amount is 0.

Request amount > 0.

BorrowableCToken_InsufficientAssetsHeld

Not enough liquidity.

Reduce loan amount or wait for more liquidity.

Transfer failure

Insufficient approval or balance.

Ensure proper approval and balance for repayment.

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

struct LeverageAction {
    IBorrowableCToken borrowableCToken;
    uint256 borrowAssets;
    ICToken cToken;
    SwapperLib.Swap swapAction;
    bytes auxData;
}
struct DeleverageStruct {
    ICToken cToken;
    uint256 collateralAssets;
    IBorrowableCToken borrowableCToken;
    uint256 repayAssets;
    SwapperLib.Swap[] swapAction;
    bytes auxData;
}

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): Interest-bearing tokens representing user deposits that can serve dual purposes:

  • Collateral: When configured as collateral, cTokens secure borrowing positions.

  • Borrowable Assets: When configured as borrowable, the underlying assets can be borrowed against collateral.

Each cToken wraps an underlying asset and maintains an exchange rate that appreciates over time as interest accrues. The isBorrowable() function determines whether a specific cToken can be borrowed, while collateral status is managed at the account level.

This unified approach simplifies the token architecture while maintaining the flexibility for assets to serve different roles within the lending protocol.

Market Manager

The Market Manager is the central contract that:

  • Manages risk parameters for all tokens in its market.

  • Handles collateral posting and borrowing interactions.

  • Coordinates liquidation processes.

  • Enforces position health requirements.

  • Monitors and applies interest rate models.

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.

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

  • Dual-Cap Risk Control: The combination of collateral caps (limiting how much can be deposited) and debt caps (limiting how much can be borrowed) provides comprehensive risk management across boths ides of the lending market.

Dynamic Liquidation Engine (DLE)

The Dynamic Liquidation Engine enables more nuanced position management:

Liquidation State Machine:

  1. Healthy Position: Collateral value exceeds required thresholds.

  2. Soft Liquidation Threshold: When collateral/debt ratio falls below soft threshold, partial liquidations begin with base penalties.

  3. Hard Liquidation Threshold: When ratio falls below hard threshold, complete liquidation is permitted with higher penalties.

  4. Bad Debt Threshold: When debt exceeds collateral value, socialized bad debt handling begins.

OEV Liquidation Auctions

Curvance features a next-generation liquidation auction system, OEV (Optimal Extractable Value) that maximizes MEV capture:

  1. High-Performance Batch Processing:

    1. Enables concurrent processing of multiple liquidations within a single transaction.

    2. Dramatically reduces latency and gas costs while maximizing throughput efficiency.

    3. Ensures rapid position resolution during market stress.

  2. Off-chain Auction Architecture:

    1. Conducts competitive bidding in a gas-efficient off-chain environment.

    2. Sophisticated bidding algorithms rank each proposal based on multiple parameters including close factor and penalty fees.

    3. Enables optimal price discovery while minimizing on-chain footprint.

  3. MEV capture:

    1. Transforms traditional MEV extraction into protocol-captured value.

    2. Eliminates wasteful gas wars through structured auction mechanisms.

    3. Provides a more efficient and equitable liquidation process.

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. Volatility-Responsive Liquidations: Aggressive liquidations in volatile periods, gentler in stable periods.

  4. Bad Debt Socialization: When a user's debt exceeds collateral, lenders share any shortfall.

The overall architecture provides a powerful framework for managing diverse asset classes while maintaining protocol solvency and capital efficiency.

Plugin Integration

This guide will walk you through the process of 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, check out our technical documentation page:

Understanding Plugin Delegation

Curvance's plugin system enables third-party applications to perform actions on behalf of users through a secure delegation mechanism. This allows for innovative features like:

  • Automated portfolio management.

  • Advanced trading strategies.

  • Cross-chain operations.

  • Reward auto-compounding.

  • Sequential action chaining.

Code Examples

Before your application can act on behalf of users, they must explicitly authorize your contract address as a delegate. This is the critical first step in the integration process.

Enabling Delegation with setDelegateApproval()

Users need to call the setDelegateApproval() function on the appropriate Curvance contract.

Calling setDelegateApproval() is called correctly with these arguments:

Type
Name
Instruction

Below is an example showing how to implement this using Ethers.js 5.7 for a position that uses PositionManagementSimple (such as pwstETH):


Verification and Security Best Practices

When implementing delegation in your application:

  1. Verify delegation status: Always check if your contract is still approved as a delegate before attempting actions by calling isDelegate()in the CentralRegistry contract using the following parameters:

Type
Name
Description
  1. Handle revocation: Users can revoke delegation at any time, so design your application to gracefully handle this case.

  2. Transparent permissions: Clearly communicate to users which actions your application will perform on their behalf.

  3. Gas optimization: Consider batching multiple delegated actions when possible to reduce gas costs.

Next Steps

After implementing the delegation setup, your application should:

  1. Verify the delegation was successful.

  2. Store the user's delegation status in your application state.

  3. Implement the specific delegated actions your application needs.

  4. Provide a way for users to revoke delegation if desired.

For technical support or to get your plugin featured in Curvance's ecosystem, please contact our developer relations team.

Deleverage / Fold

Overview

Curvance's position management system offers sophisticated leveraging and deleveraging capabilities across various asset types and DeFi protocols. This document explains the process of unwinding (deleveraging) a position.

Key Components

The deleveraging system consists of several interconnected components:

  • Position Management Base: Core contract defining the leverage/deleverage interface.

  • Protocol-Specific Implementations: Extensions for specific DeFi protocols (Pendle, Velodrome, etc.).

  • Market Manager: Tracks account positions and validates liquidity status.

  • cTokens: Manage borrowing and collateral positions.

Deleverage Process Flow

The position unwinding flow follows these key steps:

Data Flow

  1. Initiation: User calls deleverage() with parameters defining:

  2. Position Token Withdrawal:

    1. The system calls withdrawByPositionManagement() on the collateralized cToken.

    2. This withdraws the specified collateral amount and triggers callback for specialized handling.

  3. Collateral Conversion:

    1. If collateral and debt tokens differ, swapping occurs.

    2. Protocol-specific implementations define unique swapping logic.

    3. Different adapters handle various protocols (Pendle, Velodrome, Simple swaps).

  4. Debt Repayment:

    1. The converted collateral is used to repay the user's debt.

    2. The system calls repay() on the borrow token.

  5. Asset Return:

    1. Any remaining collateral underlying is transferred back to user.

    2. Any swap dust from intermediate tokens is also returned.

State Transitions

When unwinding a leveraged position, the account's state transitions through:

The system validates that:

  • Position is valid for deleveraging.

  • Repayment amount is within bounds.

  • Final position remains solvent.

  • User has permission to execute the operation.

Protocol-Specific Implementations

Curvance supports several protocol-specific position management implementations:

  • PositionManagementSimple: Basic token swap deleveraging.

  • PositionManagementVelodrome: For Velodrome/Aerodrome LP positions.

  • PositionManagementPendlePT: For Pendle Principal Tokens.

  • PositionManagementPendleLP: For Pendle LP token positions.

Each implementation provides specialized logic for handling the unique characteristics of its respective protocol when exiting positions.

Access Control and Delegation

Position unwinding operations can be initiated by:

  • The position owner directly.

  • A delegated address with approved permissions.

  • The liquidation system (for under-collateralized positions).

Slippage Protection

To prevent excessive slippage during the unwinding process:

  • Users specify acceptable slippage parameters.

  • The system performs pre and post execution checks.

  • Transactions revert if slippage exceeds user-defined limits.

Example Flow

A typical deleveraging flow:

  1. User has a leveraged ETH position against USDC debt.

  2. User calls deleverage to unwind part of this position.

  3. System withdraws ETH collateral from the collateralized cToken.

  4. ETH is swapped to USDC according to swap parameters.

  5. USDC debt is repaid to the BorrowableCToken contract.

  6. Any remaining ETH and swap dust is returned to the user.

  7. Token approvals are cleaned up.

This mechanism allows for precise management of leveraged positions while minimizing execution risk.

User Interaction Functions

deleverage()

Description: Deleverages an existing Curvance position to decrease both collateral and debt. Includes slippage protection through the checkSlippage modifier.

Contract: PositionManagement

Function signature:

Type
Name
Description

Events:


deleverageFor()

Description: Deleverages 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 deleveraged for.

Contract: PositionManagement

Function signature:

Type
Name
Description

Events:

address

delegate

The address of your platform's contract.

bool

isApproved

To activate delegation of your platform, this would be true.

const { ethers } = require("ethers");
// Import your project's contract address
const { YourProject_Address } = require("./config/addresses");
// Import pwstETH Position Management contract address from a config file
const { pwstETH_PositionManagement_Address } = require("./config/addresses");
// Import ABI from a separate file
const { pwstETH_PositionManagement_ABI } = require("./abis/pwstETHABI");

// Connect to provider (adjust as needed for your environment)
const provider = new ethers.providers.Web3Provider(window.ethereum);
// Or for a specific RPC endpoint:
// const provider = new ethers.providers.JsonRpcProvider("YOUR_RPC_URL");

// Create signer
const signer = provider.getSigner();

// Create contract instance using imported address, ABI, and signer
const cwstETHPositionManagementContract = new ethers.Contract(
  cwstETH_PositionManagement_Address, 
  cwstETH_PositionManagement_ABI, 
  signer
);

async function approveDelegate() {
  try {
    // Call the setDelegateApproval function
    const tx = await pwstETHPositionManagementContract.setDelegateApproval(
      YourProject_Address, 
      true);
    
    console.log("Transaction submitted:", tx.hash);
    
    // Wait for transaction to be mined
    const receipt = await tx.wait();
    console.log("Transaction confirmed in block:", receipt.blockNumber);
    
    return receipt;
  } catch (error) {
    console.error("Error approving delegate:", error);
    
    // Handle specific errors
    if (error.code === 'CALL_EXCEPTION') {
      if (error.reason && error.reason.includes("PluginDelegable__DelegatingDisabled")) {
        console.error("Delegation is disabled for this account");
      } else if (error.reason && error.reason.includes("PluginDelegable_InvalidParameter")) {
        console.error("Invalid parameter: Cannot delegate to yourself");
      }
    }
    
    throw error;
  }
}

address

user

The user's address.

address

delegate

Your platform's contract address.

const { ethers } = require("ethers");
// Import your project's contract address
const { YourProject_Address } = require("./config/addresses");
// Import CentralRegistry contract address from a config file
const { CentralRegistryAddress } = require("./config/addresses");
// Import ABI from a separate file
const { CentralRegistry_ABI } = require("./abis/centralRegistryABI");

// Connect to provider (adjust as needed for your environment)
const provider = new ethers.providers.Web3Provider(window.ethereum);
// Or for a specific RPC endpoint:
// const provider = new ethers.providers.JsonRpcProvider("YOUR_RPC_URL");

// Create signer
const signer = provider.getSigner();

// Create contract instance using imported address, ABI, and signer
const CentralRegistry_Contract = new ethers.Contract(
  CentralRegistryAddress, 
  CentralRegistry_ABI,
  signer
);

async function checkDelegate() {
  try {
    // Call the isDelegate function
    const isDelegate = await CentralRegistry_Contract.isDelegate(
      userAddress, 
      true);
    
    console.log("isDelegate: ", isDelegate);
    
  } catch (error) {
    console.error("Error approving delegate:", error);
    
    throw error;
  }
}
Plugin & Delegation System

Leverage

Leveraging in Curvance allows users to amplify their position by borrowing assets against their collateral and reinvesting them. This creates a more capital-efficient position with greater exposure to underlying assets. Curvance's leveraging system is built on its powerful Position Management framework, which simplifies complex DeFi operations into single, atomic transactions. This eliminates the manual loop of borrowing, swapping, and depositing that would otherwise be required to build leveraged positions.

For those that want to dive deep into the inner workings of our leverage system, check out our Position Management page here: Position Management

View functions inside ProtocolReader provide handy data needed to calculate proper swap and leverage amounts.

Core Structures for Leveraging

To understand leveraging in Curvance, you need to be familiar with two key data structures that control the leverage and deleverage operations:

LeverageAction

struct LeverageAction {
    IBorrowableCToken borrowToken;   // The cToken you want to borrow from.
    uint256 borrowAssets;            // Amount of underlying tokens to borrow.
    ICToken cToken;                  // cToken that will receive the swapped assets.
    SwapperLib.Swap swapData;        // Instructions for swapping borrowed tokens.
    bytes auxData;                   // Optional protocol-specific data.
}

DeleverageAction

struct DeleverageAction {
    ICToken cToken;                  // The cToken you're unwinding
    uint256 collateralAmount;        // Amount of cTokens to redeem
    IBorrowableCToken borrowToken;   // The cToken debt to repay
    SwapperLib.Swap[] swapData;      // Array of swaps to execute
    uint256 repayAmount;             // Amount of debt to repay
    bytes auxData;                   // Optional protocol-specific data
}

SwapperLib.Swap

Both structures use the SwapperLib.Swap structure to handle token swaps:

struct Swap {
    address target;        // Swap router address
    address inputToken;    // Token being swapped from
    address outputToken;   // Token being swapped to
    uint256 inputAmount;   // Amount to swap
    bytes call;            // Encoded swap call data
}

Important Note: LeverageStruct takes a single Swap while DeleverageStruct takes an array of Swap operations, allowing for multi-hop swaps when deleveraging.

Pre, Post, and Auxiliary Swap Operations

Pre-Swap when Leveraging

When leveraging, a swap is executed to obtain the collateral asset using the borrowed asset. You must provide the appropriate calldata hex string in the Swap.call struct member. Additionally, ensure the swap's receiver is set to the corresponding PositionManager contract.

Post-Swap when Deleveraging

During deleveraging, a swap is performed to exchange the collateral asset for the borrowed asset. You need to supply the correct calldata hex string in the Swap.call struct member. Also, make sure the swap's receiver is configured as the relevant PositionManager contract.

Auxiliary Swap for Complex Assets

When leveraging or deleveraging exotic assets, you may need to include auxiliary data to initiate the position. For instance, to enter a Pendle PT position, you must provide swap data to exchange the borrowed asset for Pendle PT via Pendle’s internal exchange.

Below is a code snippet demonstrating how to prepare this data:

const PendleData = {
    approx: {
        guessMin: ethers.utils.parseUnits("9.9", 18),
        guessMax: ethers.utils.parseUnits("10.0", 18),
        guessOffchain: ethers.utils.parseUnits("1.0", 18),
        maxIteration: 30,
        eps: ethers.utils.parseUnits("0.001", 18)
    },
    input: {
        tokenIn: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC address
        netTokenIn: ethers.utils.parseUnits(estimatedUniswapOutputAmount.toString(), 18),
        tokenMintSy: "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", // STETH address
        pendleSwap: "0x1e8b6Ac39f8A33f46a6Eb2D1aCD1047B99180AD1", // PENDLE_SWAP address
        swapData: {
            swapType: 1, // KYBERSWAP enum value
            extRouter: "0x6131B5fae19EA4f9D964eAc0408E4408b66337b5",
            extCalldata: "0xe21fd0e9...", // The calldata hex string
            needScale: false
        }
    }
};

// Create the leverage data object
const leverageAction = {
    borrowToken: cDAIAddress,
    borrowAmount: ethers.utils.parseUnits(amountForLeverage.toString(), 18),
    positionToken: cPendlePTAddress,
    auxData: null,
    swapData: swapData
};

// Encode the data
leverageAction .auxData = ethers.utils.defaultAbiCoder.encode(
    [
        "tuple(tuple(uint256 guessMin, uint256 guessMax, uint256 guessOffchain, uint256 maxIteration, uint256 eps) approx, tuple(address tokenIn, uint256 netTokenIn, address tokenMintSy, address pendleSwap, tuple(uint8 swapType, address extRouter, bytes extCalldata, bool needScale) swapData) input)",
        "address",
        "uint256"
    ],
    [
        [
            [
                PendleData.approx.guessMin,
                PendleData.approx.guessMax,
                PendleData.approx.guessOffchain,
                PendleData.approx.maxIteration,
                PendleData.approx.eps
            ],
            [
                PendleData.input.tokenIn,
                PendleData.input.netTokenIn,
                PendleData.input.tokenMintSy,
                PendleData.input.pendleSwap,
                [
                    PendleData.input.swapData.swapType,
                    PendleData.input.swapData.extRouter,
                    PendleData.input.swapData.extCalldata,
                    PendleData.input.swapData.needScale
                ]
            ]
        ],
        lpStethAddress,
        1
    ]
);

Setting Up Your Environment

Before interacting with Curvance, set up your development environment:

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

// Initialize provider and signer
const provider = new ethers.providers.JsonRpcProvider('YOUR_RPC_URL');
const signer = new ethers.Wallet('YOUR_PRIVATE_KEY', provider);

// Contract addresses
const MARKET_MANAGER = '0x...';
const POSITION_MANAGEMENT = '0x...';
const C_TOKEN_COLLATERAL = '0x...'; // Your collateral token
const C_TOKEN_DEBT = '0x...'; // Your borrow token

// Initialize contracts
const marketManager = new ethers.Contract(
  MARKET_MANAGER,
  positionManagerAbi,
  provider
);

const positionManagement = new ethers.Contract(
  POSITION_MANAGEMENT,
  [
   'function depositAndLeverage(uint256, tuple(address,uint256,address,tuple(address,address,address,uint256,bytes),bytes), uint256)',
    'function leverage(tuple(address,uint256,address,tuple(address,address,address,uint256,bytes),bytes), uint256)',
    'function deleverage(tuple(address,uint256,address,tuple[](address,address,address,uint256,bytes),uint256,bytes), uint256)'
  ],
  signer
);

Monitoring Position Health

Maintaining a healthy leverage ratio is crucial to avoid liquidation. Regularly check your position's health by using getPositionHealth() in the ProtocolReader contract. The function returns the healthiness of an account's position.

Use zero addresses and amounts to simply check an existing position.

If the value is greater than WAD (1e18), the position is healthy.

If the value is less than WAD (1e18), the position is unhealthy/liquidatable.

// Create ProtocolReader contract instance
  const protocolReader = new ethers.Contract(
    protocolReaderAddress,
    protocolReaderAbi,
    provider
  );

  async function checkPositionHealth() {
    try {
      const [positionHealth, errorCodeHit] = await
  protocolReader.getPositionHealth(
        "MARKET_MANAGER_ADDRESS",        // IMarketManager mm
        userAddress,                     // address account
        ethers.constants.AddressZero,    // address cToken
        ethers.constants.AddressZero,    // address borrowableCToken
        false,                           // bool isDeposit
        0,                               // uint256 collateralAssets
        false,                           // bool isRepayment
        0,                               // uint256 debtAssets
        0                                // uint256 bufferTime
      );

      console.log("Position Health:", positionHealth.toString());
      console.log("Error Code Hit:", errorCodeHit);

      return { positionHealth, errorCodeHit };
    } catch (error) {
      console.error("Error calling getPositionHealth:", error);
      throw error;
    }
  }

function deleverage(
        DeleverageAction calldata action,
        uint256 slippage
) external;

DeleverageStruct

deleverageData

Structure containing deleverage operation details including position token, collateral amount, borrow token, swap data, repay amount, and auxiliary data.

uint256

slippage

Slippage accepted by the user for the deleverage action, in WAD (1e18).

// In BorrowableCToken.sol
event Repay(address payer, address account, uint256 amount);
event Transfer(address indexed from, address indexed to, uint256 value);
// In MarketManager.sol
event CollateralUpdated(uint256 shares, bool increased, address account);
// Only emitted if the positionis being completely closed
event PositionUpdated(address cToken, address account, bool open);
function deleverageFor(
        DeleverageAction calldata action,
        address account,
        uint256 slippage
) external;

DeleverageStruct

deleverageData

Structure containing deleverage operation details including position token, collateral amount, borrow token, swap data, repay amount, and auxiliary data.

address

account

The account to deleverage an active Curvance position for.

uint256

slippage

Slippage accepted by the user for the deleverage action, in WAD (1e18).

// In BorrowableCToken.sol
event Repay(address payer, address account, uint256 amount);
event Transfer(address indexed from, address indexed to, uint256 value);
// In MarketManager
event CollateralUpdated(uint256 shares, bool increased, address account);
// Only emitted if the positionis being completely closed
event PositionUpdated(address cToken, address account, bool open);

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 MEV (Maximum Extractable Value) capture.

Technical Architecture

MEV Integration with Atlas

The contract integrates MEV (Maximal Extractable Value) capture:

  • MEV Auction Mechanism: When price updates make positions liquidatable, a short off-chain auction window allows liquidators to bid.

  • 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 Auction Manager's revenue logic.

  • 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: 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 Incentive: Configurable within token-specific 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).

lFactor (Liquidation Factor)

Definition: A continuous score showing how close a position is to hard liquidation.

lFactor is denominated in WAD aka 1e18.

Calculation:

  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:

  • Close factor and liquidation incentive scale linearly with lFactor, increasing as the position gets riskier.

Validation Process

Liquidation attempts undergo multiple validation checks.

For example, we check if collateral is unlocked for auction transactions:

// 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

Curvance's pricing system is designed with risk mitigation as its primary focus through a dual-oracle approach that enhances reliability and security.

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.

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

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

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:

function canLiquidate(
    uint256[] memory debtAmounts,
    address liquidator,
    address[] calldata accounts,
    IMarketManager.LiqAction memory action
) external returns (LiqResult memory, uint256[] memory);
Type
Name
Description

address[]

debtAmounts

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.

address

liquidator

The address of the account trying to liquidate accounts.

address[]

accounts

The addresses of the accounts to be liquidated.

LiqAction

action

Instructions for a liquidation action.

Return data:

Type
Description

LiqResult

Hypothetical results for a liquidation action.

uint256[]

An array containing the debt amounts to repay from accounts, in assets.


Contract Addresses

Monad Mainnet Contract Addresses

Architecture

Contract
address

CentralRegistry

0x1310f352f1389969Ece6741671c4B919523912fF

DAOTimelock

coming soon

Market

LST Markets

AprMON / WMON

caprMON / cWMON Market Manager:

0x5EA0a1Cf3501C954b64902c5e92100b8A2CaB1Ac

caprMON

0xD9E2025b907E95EcC963A5018f56B87575B4aB26

cWMON:

0xF32B334042DC1EB9732454cc9bc1a06205d184f2

cWMON DynamicIRM

0x0d44480D72B23D19673EEde88aEa03a119bc13f4

NativePositionManager:

0xe15348F8A08d4C64FD8bebA80404f60373e8A8a9

SimplePositionManager:

0x175Ee9a037ACBf7FC59f5A765384fDd229D77C94

shMON / wMON

cshMON / cWMON Market Manager:

0xE1C24B2E93230FBe33d32Ba38ECA3218284143e2

cshMON

0x926C101Cf0a3dE8725Eb24a93E980f9FE34d6230

cWMON:

0x0fcEd51b526BfA5619F83d97b54a57e3327eB183

WMON DynamicIRM

0x16cB8A09f2a17E30273b749ed178954FE63bb1BC

NativePositionManager:

0xC7B2377eF1744229698ca72C11003184FbBDF770

SimplePositionManager:

0xcB8B4ed26ffc043be04888c79aBb5984c703Fb62

sMON / wMON

csMON/ cWMON Market Manager:

0xe5970cDB1916B2cCF6185C86C174EEE2d330D05B

csMON:

0x494876051B0E85dCe5ecd5822B1aD39b9660c928

cWMON:

0xebE45A6ceA7760a71D8e0fa5a0AE80a75320D708

cWMON DynamicIRM:

0x16cB8A09f2a17E30273b749ed178954FE63bb1BC

SimplePositionManager:

0x05c91f5EfAE14b1998c82Eb3B019d4cB312136dB

Mu Digital Markets

muBOND / AUSD

cmuBOND / AUSD Market Manager:

0x830D40CDFdc494BC1A2729a7381bfCe44326c944

cmuBOND:

0x92EE4b4d33Dc61bd93a88601F29131B08aCedBF1

cAUSD:

0x2B4e0232F46E6DB4af35474c140B968EeFCB09Ec

AUSD DynamicIRM:

0x08DcB6Ad285217ef29816Ac0F813C21C54c48Cd5

SimplePositionManager:

0x1bD48D3caC298F88A4aFb8fCebA5C5F92b0b4F20

loAZND/ AUSD

cloAZND/ AUSD Market Manager:

0x7C822B093A116654F824Ec2A35CD23a3749E4f90

cloAZND:

0xf7a6AB4aF86966C141D3C5633DF658E5CDb0a735

cAUSD:

0xDaDbB2D8f9802DC458F5D7F133D053087Ba8983d

AUSD DynamicIRM:

0xf4E19DEdB240FFd9ECEDbfad88A91C34aC3095AA

SimplePositionManager:

0x1bD48D3caC298F88A4aFb8fCebA5C5F92b0b4F20

Renzo Markets

ezETH / WETH

cezETH / cWETH Market Manager:

0x83840d837E7A3E00bBb0B8501E60E989A8987c37

cezETH:

0x20f1A13BfbF85a22Aa59D189861790981372220b

cWETH:

0xa206D51C02c0202a2Eed8E6A757b49Ab13930227

cezETH DynamicIRM:

0x08DcB6Ad285217ef29816Ac0F813C21C54c48Cd5

cWETH DynamicIRM:

0xf23d194e151069d09c927E8CD8ae3e14922F2a23

SimplePositionManager:

0xc03eBa8CC42Fba089169ED42905aceC96713F803

Agora Markets

sAUSD / AUSD

csAUSD / cAUSD Market Manager:

0xBBE7A3c45aDBb16F6490767b663428c34aA341Eb

csAUSD:

0x84C5aF20b58818631164Bb7d798E457fcFACD9Ac

sAUSD:

0xfD493ce1A0ae986e09d17004B7E748817a47d73c

cAUSD DynamicIRM:

0x22dCF7dd5518F5F08ec05c59606d84698FFF07BB

VaultPositionManager:

0x953D7190e9d132776877ca86FAE718562A67fB52

SimplePositionManager:

0x52b0e0c4099AB4f43bf37D755D4a2609e770954A

earnAUSD / AUSD

cearnAUSD / cAUSD Market Manager:

0xd6365555f6a697C7C295bA741100AA644cE28545

cearnAUSD:

0x852FF1EC21D63b405eC431e04AE3AC760e29263D

csAUSD:

0xAd4AA2a713fB86FBb6b60dE2aF9E32a11DB6Abf2

cAUSD DynamicIRM:

0x46e2bA1D6CFfB180Efa17d4D5A726b599ED8B904

SimplePositionManager:

0xb96d9F8780C61e74222523D2d06FdbAB7921790A

WMON / AUSD

cWMON / cAUSD Market Manager:

0xd6365555f6a697C7C295bA741100AA644cE28545

cWMON:

0xE01d426B589c7834a5F6B20D7e992A705d3c22ED

cAUSD:

0x6E182EB501800C555bd5E662E6D350D627F504D8

cAUSD DynamicIRM:

0x72cb6a15E050427e1a6047614A47659A2C94d1bA

SimplePositionManager:

0x31ECeF2E833BBc48cF2d7F87DB711224a988680e

DeFi Bluechip Market

WMON / USDC

cWMON / cUSDC Market Manager:

0xa6A2A92F126b79Ee0804845ee6B52899b4491093

cWMON:

0x1e240E30E51491546deC3aF16B0b4EAC8Dd110D4

cUSDC:

0x8EE9FC28B8Da872c38A496e9dDB9700bb7261774

cWMON DynamicIRM:

0x50264B615175f8DBFFF417CF6D0003D2e77D87B4

cUSDC DynamicIRM:

0x1622D77E311638C77779e6D9B212E3e7D73AaFB4

SimplePositionManager:

0xF74f7E3Db3a165ab6E4F58BbD56E27b719520272

WBTC / USDC

cWBTC / cUSDC Market Manager:

0x01C4a0d396EFE982B1B103BE9910321d34e1aEA9

cWBTC:

0x3D2Ff9F862D89Ba526a0fC166bD56ABe04EF28d5

cUSDC:

0x7C9d4f1695C6282Da5e5509Aa51fC9fb417C6f1d

cWBTC DynamicIRM:

0x8a78Bd450258B7B1D42431fB904453cA43161AdE

cUSDC DynamicIRM:

0x9F57957F2CA8d0DB433e4623f8bd248293b01e86

SimplePositionManager:

0x6B84B03Bc8481b1380c3A4CdDf221d30b5E40663

WETH / USDC

cWETH / cUSDC Market Manager:

0xb3E9E0134354cc91b7FB9F9d6C3ab0dE7854BB49

cWETH:

0x8Af00fbbb2601A8F7636EabbF6243B30BEA47D50

cUSDC:

0x21aDBb60a5fB909e7F1fB48aACC4569615CD97b5

cWETH DynamicIRM:

0x8c82ccF6Ee43346425E04325Df03219cf46a7739

cUSDC DynamicIRM:

0x034f1a014946Fe4d4ea3bD36A315F775a8C1aB26

SimplePositionManager:

0x37f94A5C9F04399477D1dF851758c0E0348CB019

Oracle

Oracle Manager

Contract
address

Oracle Manager

0x32faD39e79FAc67f80d1C86CbD1598043e52CDb6

Adaptors

Contract
address

ChainlinkAdaptor

0xACfE3fCcae79445836E03c5359BB96bd352b9C00

RedstoneClassicAdaptor

0x0fA602b3e748438A3F1599206Ed6DC497ab3331E

RedstoneCoreAdaptor

0x1779e22c746b3CA505FfA636F96c4E0916D616d8

Official Plugins

Contract
address

SimpleZapper

0x5af9b7cAc0530d3C9e11C23B7A69Cce335B8C395

VaultZapper

0x6A6A087EEC9333ACc0A71782daF2ce9fc229D145

NativeVaultZapper

0x1D60A3F3f84F095b3D6001fbc135F6D42c812269

Views

Contract
address

ProtocolViewer

0xBF67b967eCcf21f2C196f947b703e874D5dB649d

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 leveraged positions across various asset types:

  • Simple Tokens: Standard ERC20 tokens.

  • Pendle LP Tokens: Liquidity provider tokens from Pendle.

  • Pendle PT Tokens: Principal tokens from Pendle.

  • Velodrome/Aerodrome LP Tokens: Liquidity provider tokens from Velodrome and Aerodrome.

  • Liquid Staking Tokens: stETH, shMON

  • ERC4626 Yield Vaults: sFRAX, sUSDe, sUSDS

Key Components

The leverage system involves several interconnected components:

  • Position Management: Base contract that handles the core leverage logic.

  • Market Manager: Manages risk parameters and validates leverage actions.

  • Swapper: Executes token swaps to convert borrowed tokens to collateral.

  • cTokens: Manage borrowing and collateral positions.

Leverage Flow

1. Initial Deposit

Users first deposit assets into a cToken contract, which represents their initial collateral position.

2. Leverage Request

A leverage request is initiated through one of the following methods:

  • leverage(): Direct leverage of an existing position.

  • depositAndLeverage(): Deposit new collateral and leverage in a single transaction..

  • leverageFor(): Leverage on behalf of another user (requires delegation)

3. Validation & Maximum Borrowing

The system checks how much the user can borrow based on:

  • Current collateral value.

  • Collateralization ratio of the collateralized cToken.

  • Existing debt.

  • Available liquidity in the BorrowableCToken market.

maxBorrowAmount = _maxRemainingLeverageOf(account, borrowToken)

4. Token Borrowing

The system borrows tokens from the specified BorrowableCToken through a callback pattern:

borrowToken.borrowForPositionManagement(account, borrowAmount, leverageData)

5. Asset Conversion

Depending on the token type, different specialized swapping mechanisms are used:

  • Simple Tokens: Direct swaps through external DEXs.

  • Pendle LP Tokens: Tokens are swapped and then added to Pendle liquidity pools.

  • Velodrome/Aerodrome LP: Tokens are swapped and then added to the appropriate DEX pools.

Each implementation handles the specific logic required for that token type.

Asset-Specific Flows

Simple Token Leverage

For standard ERC20 tokens, the process is straightforward:

  • Borrow the underlying token from an BorrowableCToken.

  • Swap the borrowed asset for the collateral cToken's underlying.

  • Deposit the resulting tokens as additional collateral.

Pendle LP Token Leverage

For Pendle LP tokens, the process involves:

  • Borrow the underlying token from a BorrowableCToken.

  • If necessary, swap the borrowed token to a valid Pendle input token.

  • Use the Pendle Router to add liquidity and mint LP tokens.

  • Deposit the resulting LP tokens as additional collateral.

Velodrome/Aerodrome LP Token Leverage

For Velodrome or Aerodrome LP tokens:

  • Borrow the underlying token from an eToken.

  • If the borrowed token isn't part of the LP pair, swap it.

  • Balance the amounts for optimal LP provision.

  • Use the appropriate router to add liquidity.

  • Deposit the resulting LP tokens as additional collateral.

Protocol Fees

When leveraging positions, users pay a protocol fee, which is calculated as:

fee = FixedPointMathLib.mulDivUp(amount, getProtocolLeverageFee(), WAD)

The fee is taken from the borrowed amount before swapping.

Slippage Protection

To protect users from unexpected price movements during the leverage process:

  • Users specify a maximum acceptable slippage parameter.

  • The system monitors the pre/post value of the user's position.

  • If the value loss exceeds the specified slippage, the transaction reverts.

This protection is implemented through the checkSlippage modifier.

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

Delegation of leverage actions requires careful permission management:

  • Only approved delegates can perform leverageFor operations.

  • Users can revoke permissions through the Central Registry.

  • The system verifies delegation status before each operation.

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 assets into a Curvance position and then leverages the position to increase both collateral and debt. Includes slippage protection through the checkSlippage modifier. The caller must have approved this contract to have delegated actions on the position token.

Contract: PositionManagement

Function signature:

function depositAndLeverage(
    uint256 assets,
    LeverageAction calldata action,
    uint256 slippage
) external;
Type
Name
Description

uint256

assets

The amount of the underlying assets to deposit.

LeverageStruct

leverageData

Structure containing leverage operation details including borrow token, amount, position token, swap data, and auxiliary data.

uint256

slippage

Slippage accepted by the user for the leverage action, in WAD (1e18).

Events:

// In cToken
event Transfer(address indexed from, address indexed to, uint256 value);
/// @dev `keccak256(bytes("Deposit(address,address,uint256,uint256)"))`.
uint256 internal constant _DEPOSIT_EVENT_SIGNATURE = 0xdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7;
// In CToken
event Transfer(address indexed from, address indexed to, uint256 value);
event Borrow(address account, uint256 amount);
// In BaseCToken
event CollateralUpdated(uint256 shares, bool increased, address account);
// Only emitted if this is the user's first position in the specific token
event PositionUpdated(address cToken, address account, bool open);

leverage()

Description: Leverages an existing Curvance position to increase both collateral and debt. Includes slippage protection through the checkSlippage modifier.

Contract: PositionManagement

Function signature:

function leverage(
    LeverageStruct calldata leverageData,
    uint256 slippage
) external checkSlippage(msg.sender, slippage) nonReentrant
Type
Name
Description

LeverageStruct

leverageData

Structure containing leverage operation details including borrow token, amount, position token, swap data, and auxiliary data.

uint256

slippage

Slippage accepted by the user for the leverage action, in WAD (1e18).

Events:

// In CToken.sol
event Transfer(address indexed from, address indexed to, uint256 value);
event Borrow(address account, uint256 amount);
// 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:

function leverageFor(
    LeverageStruct calldata leverageData,
    address account,
    uint256 slippage
) external checkSlippage(account, slippage) nonReentrant
Type
Name
Description

LeverageStruct

leverageData

Structure containing leverage operation details including borrow token, amount, position token, swap data, and auxiliary data.

address

account

The account to leverage an active Curvance position for.

uint256

slippage

Slippage accepted by the user for the leverage action, in WAD (1e18).

Events:

// In CToken.sol
event Transfer(address indexed from, address indexed to, uint256 value);
event Borrow(address account, uint256 amount);
// In BaseCToken.sol
event CollateralUpdated(uint256 shares, bool increased, address account);

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

  • 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: enforces minimum active loan size and hold period protections.

  • Dynamic Interest Rate Model: External IRM module that updates borrow/supply rates based on utilization and configured parameters.

  • Oracle Manager: Manages asset pricing through multiple oracle resources with built-in safety mechanisms and price divergence circuit breakers.

  • cTokens: ERC4626-like market tokens. Per-token toggles control whether the cToken is borrowable and/or allowed as collateral.

Market Tokens

cTokens (Curvance tokens): One listed per asset in a market.

  • Collateralization: Per-token switch to allow posting as collateral.

  • Borrowable: Per-token switch to allow borrowing of the underlying asset.

Data Flow

Collateral Flow

  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 collateral value cannot cover their debt even at a full seizure, the shortfall is recognized as bad debt and socialized to lenders in that market. The manager computes and exposes this bad debt during liquidation evaluation.

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 minimum hold period applies to sensitive actions to mitigate flash loan and short-term oracle manipulation:

    • Posting collateral

    • Borrowing

    • Repayment and any redemption.

  • The cooldown timestamp is updated on these actions; attempts before expiry will revert.

The state transition looks like:

Minimum active loan size

  • Each isolated market sets a minimum active loan size in USD terms at deployment (WAD), constrained within $10-100.

  • Hypothetical and real borrow paths enforce this to avoid uncloseable dust loans.

Pausing and Permissions

Global market toggles: liquidationPaused, transferPaused, seizePaused

Per-token action toggles: mintPaused, collateralizationPaused, borrowPaused (all queried via a consolidated actionsPaused() view function in the MarketManager.

Listing: Tokens must be listed a manager to be used for collateralization, borrowing, or liquidation.

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

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:

function isListed(address cToken) external view returns (bool)
Type
Name
Description

address

cToken

Address of the cToken.

Return data:

Type
Description

bool

Whether the cToken is registered in the lending market.


queryTokensListed()

Contract: MarketManager

Description: Returns a list of all tokens inside this market for offchain querying.

Function signature:

function queryTokensListed() external view returns (address[] memory)

Return Data:

Type
Description

address[]

Array of all cToken addresses listed in the market.


assetsOf()

Contract: MarketManager

Description: Returns all assets that an account has entered.

Function signature:

function assetsOf(address account) external view returns (address[] memory)
Type
Name
Description

address

account

The address of the account to query.

Return data:

Type
Description

IMToken[]

Array of cTokens that the account has entered.


Status and Liquidity

statusOf()

Contract: MarketManager

Description: Determine the account's current status between collateral and additional liquidity in USD.

Function signature:

function statusOf(address account) external view returns (uint256, uint256, uint256)
Type
Name
Description

address

account

The account to determine liquidity for.

Return data:

Type
Description

uint256

Total value of account collateral.

uint256

The maximum amount of debt the account could take on based on an account's collateral.

uint256

Total value of an account's debt.


Permission and Validation

canMint()

Contract: MarketManager

Description: Checks if an account is allowed to mint tokens in the specified market, validating system parameters and account state. Transaction succeeds if true, reverts if false. Used by cTokens to check if minting is paused.

Function signature:

function canMint(
    address cToken) external view
Type
Name
Description

address

cToken

The Curvance token address to verify minting for.


canRedeem()

Contract: MarketManagerIsolated

Description: Checks if an account is allowed to redeem their tokens in the given market. Verifies that redeeming is not paused, and transfers aren't disabled in the CentralRegistry. Transaction succeeds if true, reverts if false. Used by cTokens to check if redeeming is paused.

Function signature:

function canRedeem(
    address cToken,
    address account, 
    uint256 amount) external view returns (uint256, bool[] memory)
Type
Name
Description

address

cToken

The market token to verify redemption for.

uint256

amount

The number of tokens to redeem for the underlying asset.

address

account

The account which would redeem the tokens.


canRepay()

Contract: MarketManager

Description: Checks if a loan repayment is allowed for an account in the given market. Reverts if false, succeeds if true.

Function signature:

function canRepay(address cToken, address account) external view
Type
Name
Description

address

cToken

The market token to verify the repayment for.

address

account

The account who will have their loan repaid.


Paused States

actionsPaused()

Contract: MarketManager

Description: Checks whether minting, collateralization, borrowing of cToken is paused.

function actionsPaused(
    address cToken
) external view returns (bool, bool, bool);

Inputs:

Type
Description

address

Address of the cToken to check the pause statuses of.

Return data:

Type
Description

bool

Whether minting cToken is paused or not.

bool

Whether collateralization of cToken is paused or not.

bool

Whether borrowing cToken is paused or not.


Market Invariants

Contract: MarketManager

Description: Returns the amount of cTokens that has been posted as collateral in shares.

mapping(address => uint256) public collateralPosted;

Inputs:

Type
Description

address

Address of the cToken to check the amount of collateral posted.

Return data:

Type
Description

uint256

amount of cTokens that has been posted as collateral in shares.


collateralCaps()

Contract: MarketManager

Description: Returns the amount of cToken that can be posted of collateral, in shares.

mapping(address => uint256) public collateralCaps;

Inputs:

Type
Description

address

Address of the cToken to check the max amount of shares that can be posted as collateral.

Return data:

Type
Description

uint256

amount of cTokens that has can be posted as collateral.


debtCaps()

Contract: MarketManager

Description: Returns the amount of cToken that can be borrowed, in underlying assets.

mapping(address => uint256) public debtCaps;

Inputs:

Type
Description

address

Address of the cToken

Return data:

Type
Description

uint256

amount of cTokens that can be borrowed.

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 upon the cToken foundation by extending BaseCTokenWithYield.

  • BaseCTokenWithYield: Provides the core vault functionality with yield distribution mechanisms.

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

Interest Rate Mechanism

BorrowableCTokens employ a dynamic interest rate system that responds to market conditions:

  • Utilization-Based rates: Interest rates adjust based on the ratio of borrowed assets to total supplied assets.

  • Dynamic Adjustments: The DynamicIRM continuously monitors market conditions and adjusts rates at regular intervals.

  • Dual Rate System:

    • Borrow Rate:

    • Supply Rate:

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. User must first have collateral posted in compatible CTokens

  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. User's redemption ability is paused for 20 minutes (anti-flash loan protection).

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 the user to the contract.

  5. Assets are transferred to repay debt.

  6. User's debt position is updated or closed.

  7. MarketManager is notified of debt reduction.

Interest Accrual Flow

  1. _accrueIfNeeded() is called before most operations.

  2. Time elapsed since last accrual is calculated.

  3. Interest rates are fetched 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:

  • Health Factor Monitoring: Continuous monitoring of borrower collateral ratios through oracle price feeds.

  • Liquidation Triggers: Positions become liquidatable when collateral value falls below required thresholds.

  • Partial Liquidations: Liquidators can repay specific amounts for bad debt when collateral is insufficient.

  • 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: Implements IFlashLoan interface for custom logic execution.

  • Use Cases: Arbitrage, liquidations, collateral swapping, and other DeFi strategies.

Position Manager Integration

BorrowableCTokens integrate with Position Managers for sophisticated strategies:

  • Leveraged Positions: Automated borrowing and collateral posting for leverage.

  • Cross-Market Operations: Coordinated actions across multiple markets.

  • Slippage Protection: Built-in protection for complex multi-step operations.

  • Callback Mechanisms: Position Managers can execute custom logic during borrow operations.

Security Features

BorrowableCTokens implement multiple security layers:

  1. Debt-Collateral Separation: Users cannot post BorrowableCTokens as collateral if they have outstanding debt in the same market.

  2. Pause Mechanisms: Independent pausing of borrowing, lending, and liquidation functions.

  3. Reentrancy Protection: Comprehensive guards against reentrancy attacks.

  4. Interest Rate Limits: Maximum bounds on interest rates to prevent exploitation.

  5. Oracle Integration: Multiple price feed validation for liquidation accuracy.

  6. Flash Loan Protection: 20-minute withdrawal delays after borrowing to prevent flash loan attacks.

Protocol Revenue

BorrowableCTokens generate protocol revenue through multiple mechanisms:

  • Interest Fees: Configurable percentage of borrower interest payments.

  • Flash Loan Fees: 0.04% fee on all flash loan amounts.

  • Liquidation Fees: Portion of liquidation bonuses directed to protocol reserves.

  • Fee Distribution: Protocol fees are accumulate and can be withdrawn by governance.

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: Multiple price feeds and staleness protection.

Interest Rate Bounds: Maximum and minimum rate limits to prevent extreme scenarios.

Integration Considerations

State Updates: Always call functions that update interest before relying on debt or balance information.

Market Conditions: Interest rates and liquidation thresholds can change based on market utilization.

User Interaction Functions

Borrow Functions

borrow()

Contract: BorrowableCToken.sol

Description: Borrows underlying tokens from lenders, based on collateral posted inside this market.

Function signature:

Type
Name
Description

Event:


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:

Type
Name
Description

Event:


Borrow Functions

Contract: BorrowableCToken.sol

Description:

Function signature:

Type
Name
Description

Event:


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:

Type
Name
Description

Event:


repayFor()

Contract: BorrowableCToken.sol

Description: Repays underlying tokens to lenders, on behalf of account, freeing up their collateral posted inside this market.

Function signature:

Type
Name
Description

Event:


Debt Tracking

debtBalance()

Contract: BorrowableCToken.sol

Description: Returns the current debt balance for a given account without accruing interest. This is a view function that uses the cached exchange rate.

Function signature:

Type
Name
Description

Return data:

Type
Description

debtBalanceUpdated()

Contract: BorrowableCToken.sol

Description: Returns the current outstanding debt balance of an account with pending interest applied via _accrueIfNeeded().

Function signature:

Type
Name
Description

Return data:

Type
Description
IDynamicIRM public IRM;
uint240 public marketOutstandingDebt;
uint16 public interestFee;
/// @notice Active debt information associated with an account.
mapping(address => uint256) internal _debtOf;
uint256 internal _vestingData;
function borrow(uint256 amount) external

uint256

amount

The amount of the underlying asset to borrow

// From cToken
event Borrow(uint256 assets, address account);
// Emitted from cToken and underlying ERC20 asset.
event Transfer(address indexed from, address indexed to, uint256 value);
// Potentially emitted multiple times, if this is the first time borrowing this cToken,
// and for any positions that need to be closed after borrowing.
event PositionUpdated(address cToken, address account, bool open);
function borrowFor(
        address account,
        address recipient,
        uint256 amount
    ) external nonReentrant

address

account

The account who will have their assets borrowed against

address

recipient

The account who will receive the borrowed assets

uint256

amount

The amount of the underlying asset to borrow

// From BorrowableCToken.sol
event Borrow(uint256 assets, address account);
// Emitted from cToken and underlying ERC20 asset.
event Transfer(address indexed from, address indexed to, uint256 value);
// Potentially emitted multiple times, if this is the first time borrowing this eToken,
// and for any positions that need to be closed after borrowing.
event PositionUpdated(address cToken, address account, bool open);
function flashLoan(uint256 assets, bytes calldata data) external

uint256

assets

The amount of asset() loaned during the flashloan.

bytes

data

Arbitrary calldata passed to flashloan callback to execute desired action during the flashloan.

event Flashloan(uint256 assets, uint256 assetsFee, address account);
function repay(uint256 amount) external nonReentrant

uint256

amount

The amount of the underlying asset to repay, or 0 for the full outstanding amount

// From BorrowableCToken.sol
event Repay(uint256 assets, address payer, address account);
// Emitted from underlying ERC20 asset transfer.
event Transfer(address indexed from, address indexed to, uint256 value);
// From MarketManager
// Potentially emitted if a position is fully closed.
event PositionUpdated(address cToken, address account, bool open);
function repayFor(address account, uint256 amount) external nonReentrant

address

account

The account address to repay on behalf of.

uint256

amount

The amount to repay, or 0 for the full outstanding amount.

// From BorrowableCToken.sol
event Repay(uint256 assets, 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);
// From MarketManager
// Potentially emitted if a position is fully closed.
event PositionUpdated(address cToken, address account, bool open);
    function debtBalance(
        address account
    ) external nonReentrant returns (uint256 result)

address

account

The address whose debt balance should be calculated

uint256

The current balance of debt for the account

    function debtBalanceUpdated(
        address account
    ) external nonReentrant returns (uint256 result)

address

account

The address whose debt balance should be calculated

uint256

The estimated debt balance at the specified timestamp

Orderflow Auction System

Overview

The Curvance orderflow auction system combines off-chain liquidation auctions with on-chain enforcement to enable:

  • SOON: 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.

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

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.

    /// @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;
   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

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

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;
    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)
        }
    }
    function resetTransientLiquidationConfig() external {
        _checkAuctionPermissions();

        /// @solidity memory-safe-assembly
        assembly {
            tstore(_TRANSIENT_LIQUIDATION_CONFIG_KEY, 0)
        }
    }
    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);
    }

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

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.

Note: No auction buffer is applied in non-auction liquidations.

// 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;
        }
// 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.

    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)
        }
    }
    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).

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

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.

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

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.

For a full list of delegable actions, please check out our plugin guide here:

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:

Type
Name
Description

Return data:

Type
Description

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:

Type
Name
Description

Events:


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:

Type
Name
Description

Return data:

Type
Name
Description

checkDelegationDisabled()

Description: Checks whether delegation is temporarily or permanently disabled for a specified user.

Contract: CentralRegistry

Function signature:

Type
Name
Description

Return data:

Type
Description

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:

Type
Name
Description

Return data:

Type
Description

incrementApprovalIndex()

Description: Increments a caller's approval index, immediately revoking all delegate permissions across all Curvance contracts.

Contract: CentralRegistry

Function signature:

Events:


checkDelegationDisabled()

Description: Determines whether a user has delegation disabled, either intentionally or due to a cooldown period.

Function signature:

Type
Name
Description

Return data:

Type
Description

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:

Parameters:

Type
Name
Description

Events:

function isDelegate(address user, address delegate) public view returns (bool)

address

user

The address to check whether delegate has delegation permissions for.

address

delegate

The address to check delegation permissions of user.

bool

Returns true if delegate is authorized to act on behalf of user.

function setDelegateApproval(address delegate, bool isApproved) external

address

delegate

The address that will be approved or restricted from delegated actions.

bool

isApproved

Whether delegate is being approved (true) or restricted (false).

event DelegateApproval(
    address indexed owner,
    address indexed delegate,
    uint256 approvalIndex,
    bool isApproved
);
function getUserApprovalIndex(address user) public view returns (uint256)

address

user

The user to check delegated approval index for.

address

user

The user to check delegated approval index for.

function checkDelegationDisabled(address user) public view returns (bool)

address

user

The user to check delegation status for.

bool

Returns true if user has delegation disabled or if their cooldown period has not expired.

function getUserApprovalIndex(address user) external view returns (uint256)

address

user

The user to check delegated approval index for.

uint256

Current approval index for the specified user.

function incrementApprovalIndex() external
event ApprovalIndexIncremented(address indexed user, uint256 newIndex);
function checkDelegationDisabled(address user) external view returns (bool)

address

user

The user to check delegation status for.

bool

Returns true if user has delegation disabled or if their cooldown period has not expired.

function setDelegable(bool delegationDisabled) external

bool

delegationDisabled

true to disable delegation, false to enable delegation (after cooldown).

event DelegableStatusChanged(
    address indexed user,
    bool delegable,
    uint256 delegationEnabledTimestamp
);
List of Delegable Actions

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.

Core Components

Interest Rate Structure

The model uses two primary interest rate components:

  • Base Interest Rate: Applied linearly as utilization increases from 0% to the vertex point, which increases linearly until a certain utilization threshold (vertexStartingPoint) is reached.

  • Vertex Interest Rate: Applied when utilization exceeds the vertex point, multiplied by the Vertex Multiplier, which adjusts more steeply based on liquidity utilization.

  • Vertex Starting Point: The utilization threshold where the model switches from base to vertex rate.

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.

Data Flow

  1. Market Utilization → Calculated from Debt/(AssetsHeld + Debt).

  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 → Adjusted on each call by the linked token; cadence is ADJUSTMENT_RATE.

  5. Decay Mechanism → Continuously reduces elevated multiplier values over time.

State Machine

The Vertex Multiplier operates as a state machine with the following transitions:

States and Transitions

  • Initialization State

    • Starting point: vertexMultiplier = WAD (1.0).

    • Next update timestamp set.

  • Adjustment States

    • Based on utilization relative to thresholds:

      • Below decreaseThresholdMax: Maximum negative adjustment + decay.

      • Below vertex but above decreaseThresholdMax: Scaled negative adjustment + decay.

      • Above vertex but below increaseThreshold: Only decay applied.

      • Above increaseThreshold: Positive adjustment + 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.

Configuration Parameters

The model is governed by several parameters that define its behavior:

  • Base Parameters:

    • baseRatePerSecond: Rate at which interest is accumulated, before vertexStart .

    • vertexInterestRate: Rate at which interest is accumulated, after vertexStart.

    • vertexStart: The utilization point where vertex takes effect.

  • Adjustment Controls:

    • ADJUSTMENT_RATE: Fixed time between multiplier updates in seconds.

    • adjustmentVelocity: Maximum rate of multiplier change per update.

    • vertexMultiplierMax: Maximum allowed value for multiplier.

  • Threshold Parameters:

    • increaseThresholdStart: Point above vertex where multiplier increases.

    • decreaseThresholdEnd: Point above vertex where multiplier decreases.

    • decayRate: Rate at which elevated multipliers naturally decrease.

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. Self-Balancing:

    1. Creates a feedback loop that stabilizes market liquidity.

    2. Prevents liquidity crunches through predictive rate adjustments.

  3. Growth Incentives:

    1. Decay mechanism helps maintain competitive rates during normal operations.

    2. Creates naturally decreasing interest rates in stable markets.

  4. Gas Optimization:

    1. Uses bit-packed storage for multiplier and timestamp.

    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 ensures rates will eventually normalize even without full utilization correction.

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

UtilizationRate=Debt/(AssetsHeld+Debt)Utilization Rate = Debt / (AssetsHeld + Debt)UtilizationRate=Debt/(AssetsHeld+Debt)
    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);
    }

  1. Base Interest Rate Calculation

BaseRate=(Utilization∗baseRatePerSecond)/WADBase Rate = (Utilization * baseRatePerSecond) / WADBaseRate=(Utilization∗baseRatePerSecond)/WAD
    function _baseRate(
        uint256 util,
        uint256 baseRatePerSecond
    ) internal pure returns (uint256 r) {
        r = _mulDiv(util, baseRatePerSecond, WAD);
    }

  1. Vertex Interest Rate Calculation

VertexInterestRate=((vertexStart∗baseRatePerSecond)/WAD)+((utilization−vertexStart)∗vertexRatePerSecond∗vertexMultiplier)/WAD2VertexInterestRate = ((vertexStart * baseRatePerSecond) / WAD) + ((utilization - vertexStart) * vertexRatePerSecond * vertexMultiplier) / WAD^2VertexInterestRate=((vertexStart∗baseRatePerSecond)/WAD)+((utilization−vertexStart)∗vertexRatePerSecond∗vertexMultiplier)/WAD2
    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
        );
    }

  1. Final Borrow Interest Rate Calculation

BorrowRate=BaseInterestRate+VertexInterestRateBorrow Rate = Base Interest Rate + Vertex Interest RateBorrowRate=BaseInterestRate+VertexInterestRate
    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
        );
    }

  1. Vertex Multiplier Adjustment (Above Vertex)

decay:

decay=(multiplier∗decayPerAdjustment)/BPSdecay = (multiplier * decayPerAdjustment) / BPSdecay=(multiplier∗decayPerAdjustment)/BPS

start (converted to WAD):

start=increaseThresholdStart∗1e14start = increaseThresholdStart * 1e14 start=increaseThresholdStart∗1e14

Case 1: Low Utilization (utilization <= start)

newMultiplier=max((multiplier−decay),WAD)newMultiplier = max((multiplier - decay), WAD)newMultiplier=max((multiplier−decay),WAD)

Case 2: High Utilization (utilization > start)

shift=((util−start)∗WAD)/(WAD−start)shift = ((util - start ) * WAD) / (WAD - start)shift=((util−start)∗WAD)/(WAD−start)
adjustedShift=WADBPS+shift∗adjustmentVelocity)adjustedShift = WADBPS+ shift * adjustmentVelocity)adjustedShift=WADBPS+shift∗adjustmentVelocity)
newMultiplier=(multiplier∗adjustedShift)/WADBPS−decaynewMultiplier = (multiplier * adjustedShift) / WADBPS - decaynewMultiplier=(multiplier∗adjustedShift)/WADBPS−decay
finalResult=min(max(newMultiplier,WAD),vertexMultiplierMax)finalResult = min(max(newMultiplier, WAD), vertexMultiplierMax)finalResult=min(max(newMultiplier,WAD),vertexMultiplierMax)
    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 <= (1e14 * 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`.
            1e14 * 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;
    }
    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;
    }

  1. Vertex Multiplier Adjustment (Below Vertex)

decay:

decay=(multiplier∗decayPerAdjustment)/BPSdecay = (multiplier * decayPerAdjustment) / BPSdecay=(multiplier∗decayPerAdjustment)/BPS

end (converted to WAD):

end=decreaseThresholdEnd∗1e14end = decreaseThresholdEnd * 1e14end=decreaseThresholdEnd∗1e14

Case 1: Very Low Utilization (utilization <= end)

newMultiplier=max((multiplier∗BPS)/(BPS+adjustmentVelocity)−decay,WAD)newMultiplier = max((multiplier * BPS) / (BPS + adjustmentVelocity) - decay, WAD)newMultiplier=max((multiplier∗BPS)/(BPS+adjustmentVelocity)−decay,WAD)

Case 2: Moderate Utilization (utilization > end)

shift=((vertexStart−util)∗WAD/(vertexStart−end)shift = ((vertexStart - util) * WAD / (vertexStart - end)shift=((vertexStart−util)∗WAD/(vertexStart−end)
adjustedShift=WADBPS+(shift∗adjustmentVelocity)adjustedShift = WADBPS + (shift * adjustmentVelocity)adjustedShift=WADBPS+(shift∗adjustmentVelocity)
newMultiplier=(multiplier∗WADBPS)/adjustedShift−decaynewMultiplier = (multiplier * WADBPS) / adjustedShift - decaynewMultiplier=(multiplier∗WADBPS)/adjustedShift−decay
result=max(newMultiplier,WAD)result = max(newMultiplier, WAD)result=max(newMultiplier,WAD)

    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 <= (1e14 * 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`.
            1e14 * 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;
    }
    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 Interaction 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:

function predictedBorrowRate(
    uint256 assetsHeld,
    uint256 debt
    ) public view returns (uint256 result)
Type
Name
Description

uint256

assetsHeld

The amount of underlying assets held in the pool.

uint256

debt

The amount of outstanding debt in the pool.

Return data:

Type
Description

uint256

The borrow rate percentage per second, in WAD.


borrowRate()

Contract: DynamicIRM

Description: Calculates the current borrow rate per second based on current market conditions.

Function signature:

function borrowRate(
    uint256 assetsHeld,
    uint256 debt
) public view returns (uint256 r)
Type
Name
Description

uint256

assetsHeld

The amount of underlying assets held in the pool.

uint256

debt

The amount of outstanding debt in the pool.

Return data:

Type
Description

uint256

The borrow interest rate percentage, per second, in WAD.


supplyRate()

Contract: DynamicIRM

Description: Calculates the current supply rate, per second. This function's intention is for frontend data querying and should not be used for onchain execution.

Function signature:

function borrowRate(
    uint256 assetsHeld,
    uint256 debt
) public view returns (uint256 r)
Type
Name
Description

uint256

assetsHeld

The amount of underlying assets held in the pool.

uint256

debt

The amount of outstanding debt in the pool.

uint256

interestFee

The current interest rate protocol fee for the market token, in BPS.

Return data:

Type
Description

uint256

The borrow interest rate percentage, per second, in WAD.


utilizationRate()

Contract: DynamicIRM

Description: Calculates the utilization rate of the market using formula: debt / (assetsHeld + debt).

Function signature:

function borrowRate(
    uint256 assetsHeld,
    uint256 debt
) public view returns (uint256 r)
Type
Name
Description

uint256

assetsHeld

The amount of underlying assets held in the pool.

uint256

debt

The amount of outstanding debt in the pool.

Return data:

Type
Description

uint256

The utilization rate between [0, WAD].


vertexMultiplier()

Contract: DynamicIRM

Description: The dynamic value applied to vertexRatePerSecond, increasing it as utilizationRate remains elevated over time, adjusted every ADJUSTMENT_RATE, in WAD.

Function signature:

// uint256 public vertexMultiplier;
function vertexMultiplier() public view returns (uint256);

Return data:

Type
Description

uint256

The utilization rate between [0, WAD].


linkedToken()

Contract: DynamicIRM

Description: The borrowable Curvance token linked to this interest rate model contract.

Function signature:

function linkedToken() external view returns (address result);

Return data:

Type
Description

uint256

The linked borrowableCToken address.


Protocol Reader

Overview

ProtocolReader is a comprehensive auxiliary contract designed for efficiently querying data from the Curvance ecosystem. It serves as the primary interface for data aggregators, frontends, and external systems to retrieve protocol information with minimal RPC calls.

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: Real-time prices, interest rates, utilization rates, liquidity levels.

    • User Data: Individual positions, collateral balances, debt positions, and veCVE locks.

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, collateral, debt, veCVE locks) to minimize RPC calls for frontend dashboards.

Function signature:

function getAllDynamicState(
    address account
) public view returns (
    DynamicMarketData[] memory market,
UserData memory user
 );
Type
Name
Description

address

account

The address of the account to get market data for.

Return data:

Type
Description

DynamicMarketData[]

Real-time market information including current prices, interest rates, liquidity, and utilization for all tokens across all markets.

UserData

The user's veCVE locks and position data across all markets including collateral, debt, health, and token balances.


getStaticMarketData()

Description: Returns immutable configuration data for all markets including token details, collateral requirements, debt caps, liquidation parameters, and oracle adapter types.

Function signature:

function getStaticMarketData() public view returns (StaticMarketData[] memory data);

Return data:

Type
Description

StaticMarketData[]

The address of the MarketManager, unique oracle adaptors, the market's cooldown length and tokens involved in the market.


getDynamicMarketData()

Description: Returns real-time data for all markets

Function signature:

function getDynamicMarketData() public view returns (DynamicMarketData[] memory data)

Return data:

Type
Description

DynamicMarketData[]

Real-time market information including current prices, interest rates, liquidity, and utilization for all tokens across all markets.


getUserData()

Description: Returns the current outstanding borrows across all Curvance markets.

Function signature:

function getUserData(address account) public view returns (UserData memory data)

Return data:

Type
Description

uint256

The current outstanding borrows inside Curvance, in WAD (18 decimals)

Price Oracle

getPrice()

Description:

Function signature:

function getPrice(
    address asset, 
    bool inUSD, 
    bool getLower) 
    public view returns(
    uint256 price, 
    uint256 errorCode
);
Type
Name
Description

address

asset

Asset address to get prices for

bool

inUSD

Boolean whether to return prices in USD or the native token

bool

getLower

Boolean indicating whether to get the lower or higher price

Return data:

Type
Description

uint256

Asset price in WAD ($1 = 1e18)

uint256

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.


getPriceSafely()

Description:

Function signature:

function getPriceSafely(
    address asset,
    bool inUSD,
    bool getLower,
    uint256 errorCodeBreakpoint
) public view returns (uint256);
Type
Name
Description

address

asset

Asset address to get prices for

bool

inUSD

Boolean whether to return prices in USD or the native token

bool

getLower

Boolean indicating whether to get the lower or higher price

uint256

errorCodeBreakpoint

The minimum error code threshold that causes the function to revert - set to 1 to revert on any oracle issues, 2 to only revert on severe issues, or 3+ to never revert.

Return data:

Type
Description

uint256

Asset price in WAD ($1 = 1e18)

Position Health & Risk Assessment

getPositionHealth()

Description: Gets the Position health of account inside a market.

Function signature:

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) {
Type
Name
Description

IMarketManager

mm

The MarketManager to pull data from.

address

account

The address of the market token.

address

cToken

The number of shares to hypothetically redeem.

address

borrowablecToken

Any additional time buffer debt accrual is expected before a user's action, in seconds.

bool

isDeposit

Whether collateralAssets is for a deposit (true) or redemption (false).

uint256

collateralAssets

The amount of assets for a hypothetical action with cToken.

bool

isRepayment

Whether debtAssets is for a repayment (true) or a borrow (false).

uint256

debtAssets

The amount of assets for a hypothetical action with borrowableCToken

uint256

bufferTime

Any additional time buffer debt accrual is expected before a user's action, in seconds.

Return data:

Type
Description

uint256

positionHealth

The healthiness of account's position inside the market.

uint256

errorCodeHit

Whether an error code was hit or not, which would provide incorrect Position Health


hypotheticalRedemptionOf()

Description: Determine what the account liquidity would be if the given shares were redeemed.

Function signature:

function hypotheticalRedemptionOf(
    address account,
    address cTokenModified,
    uint256 redemptionShares,
    uint256 bufferTime
) public view returns (uint256, uint256, bool, bool) {
Type
Name
Description

address

account

The account to determine liquidity for.

address

cTokenModified

The address of the market token

uint256

redemptionShares

The number of shares to hypothetically redeem.

uint256 bu

bufferTime

Any additional time buffer debt accrual is expected before a user's action, in seconds.

Return data:

Type
Description

uint256

Hypothetical account liquidity in excess of collateral requirements.

uint256

Hypothetical account liquidity deficit below collateral requirements.

bool

The amount of collateral posted or debt owed by the account.

bool

Whether an error code was hit.


maxRedemptionOf()

Description: Determine the maximum amount of cTokenRedeemed that account can redeem.

Function signature:

function maxRedemptionOf(
    address account,
    address cTokenRedeemed,
    uint256 bufferTime
) public view returns (
    uint256 collateralizedSharesRedeemable,
    uint256 uncollateralizedShares,
    bool errorHit
);
Type
Name
Description

address

account

The account to determine redemptions for.

address

cTokenRedeemed

The cToken to redeem.

uint256

bufferTime

Any additional time buffer debt accrual is expected before a user's action, in seconds.

Return data:

Type
Description

uint256

The amount of collateralized cTokenModified shares redeemable by account.

uint256

The amount of uncollateralized cTokenModified shares redeemable by account.

bool

Whether an error code was hit.


debtBalanceAtTimestamp()

Description: Returns the debt balance of account at timestamp. marketMultiCooldown

Function signature:

function debtBalanceAtTimestamp(
    address account,
    address borrowableCToken,
    uint256 timestamp
) public view returns (uint256 debtBalance);
Type
Name
Description

address

account

The address whose debt balance should be calculated.

address

borrowableCToken

The token that account's debt balance will be checked for.

uint256

timestamp

The unix timestamp to calculate account debt balance with.

Return data:

Type
Description

uint256

The debt balance of account at timestamp.

getLiquidationPrice()

Description: Determines the collateral price necessary to trigger liquidation.

Function signature:

function getLiquidationPrice(
    address account,
    address cToken,
    bool long
) public view returns (uint256 price, bool errorHit) {
Type
Name
Description

address

account

The account address to check.

address

cToken

The address of the cToken that is being used as collateral.

bool

long

If the position is long or short.

Return data:

Type
Description

uint256

price

The collateral price to liquidate.

uint256

errorCodeHit

Whether an oracle error code was hit or not.

Hypothetical Simulation Functions

hypotheticalRedemptionOf()

Description: Determine what the account liquidity would be if the given shares were redeemed.

Function signature:

function hypotheticalRedemptionOf(
    address account,
    address cTokenModified,
    uint256 redemptionShares
) public view returns (uint256, uint256, bool, bool)
Type
Name
Description

address

account

The account to determine liquidity for.

address

cTokenModified

The cToken to hypothetically redeem.

uint256

redemptionShares

The number of shares to hypothetically redeem.

Return data:

Type
Description

uint256

Hypothetical account liquidity in excess of collateral requirements.

uint256

Hypothetical account liquidity deficit below collateral requirements.

bool

Whether the proposed action is not possible. NOT whether it passes liquidity constraints or not.

bool

Whether an error code was hit or not.


hypotheticalBorrowOf()

Description: Determine what the account liquidity would be if the given assets were borrowed.

Function signature:

function hypotheticalBorrowOf(
    address account,
    address borrowableCTokenModified,
    uint256 borrowAssets
) public view returns (uint256, uint256, bool, bool, bool)
Type
Name
Description

address

account

The account to determine liquidity for.

address

borrowableCTokenModified

The borrowableCToken to hypothetically borrow.

uint256

borrowAssets

The number of assets to hypothetically borrow.

Return data:

Type
Description

uint256

Hypothetical account liquidity in excess of collateral requirements.

uint256

Hypothetical account liquidity deficit below collateral requirements.

bool

Whether the proposed action is possible. NOT whether it passes liquidity constraints or not.

bool

Whether the desired loan size is insufficient causing an error.

bool

Whether an error code was hit or not.


hypotheticalLeverageOf()

Description: Calculates the hypothetical maximum amount of borrowableCToken assets account can borrow for maximum leverage based on a new cToken collateralized deposit.

Function signature:

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
    )
Type
Name
Description

address

account

The account to query maximum borrow amount for.

address

cToken

The token that account will deposit to leverage against.

address

borrowableCToken

The token that account will borrow assets from to achieve leverage.

uint256

assets

The amount of cToken underlying that account will deposit to leverage against.

uint256

bufferTime

Any additional time buffer debt accrual is expected before a user's action, in seconds.

Return data:

Type
Description

uint256

Returns the current leverage multiplier of account, in WAD.

uint256

Returns the maximum leverage multiplier of account after a hypothetical deposit action, adjusted by liquidity constraints, in WAD.

uint256

Returns the maximum leverage multiplier of account after a hypothetical deposit action, in WAD.

uint256

Returns the maximum remaining borrow amount allowed from borrowableCToken, measured in underlying token amount, after the new hypothetical deposit.

bool

Whether the desired loan size is insufficient causing an error.

bool

Whether an oracle error was hit when pricing assets.

Market Analysis Functions

marketMultiCooldown()

Description: Returns the cooldown periods for multiple markets for a user.

Function signature:

function marketMultiCooldown(
    address[] calldata markets,
    address user
) public view returns (uint256[] memory)
Type
Name
Description

address[]

markets

The list of market addresses.

address

user

The user address.

Return data:

Type
Description

uint256[]

The list of cooldown periods for each market.


previewAssetImpact()

Description: Returns the outstanding underlying tokens borrowed from an EToken market.

Function signature:

function previewAssetImpact(
    address user,
    address collateralCToken,
    address debtBorrowableCToken,
    uint256 newCollateralAssets,
    uint256 newDebtAssets
) public view returns (uint256 supply, uint256 borrow);
Type
Name
Description

address

user

The address of the user account to preview asset impact of.

address

collateralCToken

The address of the collateral cToken.

address

debtBorrowableCToken

The address of the debt borrowable cToken.

uint256

newCollateralAssets

The amount of new collateral asset to deposit, in assets.

uint256

newDebtAssets

The amount of new debt asset to borrow, in assets.

Return data:

Type
Description

uint256

The projected supply rate, in WAD seconds.

uint256

The projected borrow amount, in WAD seconds.


cToken

Overview

CTokens are Curvance's unified vault tokens that represent user deposits in various yield-generating strategies. CTokens are designed to be fully liquid, ensuring that assets can be immediately withdrawn or liquidated when needed, while providing seamless integration with the broader Curvance lending ecosystem.

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.

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 while maintaining full ERC4626 compliance.

Strategy cTokens

Strategy CTokens (StrategyCToken) extend basic functionality by adding automated yield optimization features. These sophisticated vaults:

  • Automatically harvest rewards from underlying protocols

  • Convert rewards back into the underlying asset through optimized swap routes

  • Reinvest proceeds into the yield-bearing position

  • Distribute yield to all vault users through a gradual vesting mechanism

  • Optimize gas costs through batched operations and keeper automation

BorrowableCTokens

See 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 Management

CTokens maintain several key state variables to track vault operations and user positions:

uint256 internal _totalAssets
uint256 internal constant _BALANCE_SLOT_SEED = 0x87a211a2;
uint256 public vestingPeriod;
// Only present in StrategyCTokens and BorrowableCTokens
uint256 internal _vestingData;

Data Flow

Deposit Flow

  1. User calls deposit() with underlying assets.

  2. Contract calculates shares based on current exchange rate.

  3. For strategy vaults, the _afterDeposit() hook stakes tokens in external protocols.

  4. Underlying tokens are transferred from user to contract.

  5. Vault shares (cTokens) are minted to the user.

  6. If user selects collateral usage, the vault notifies the MarketManager.

  7. Position is registered and available for borrowing against.

Withdrawal Flow

  1. User calls withdraw() with the amount of assets to withdraw.

  2. For strategy vaults, the _beforeWithdraw() hook is called to unstake tokens from the external protocol.

  3. Contract calculates shares to burn based on current exchange rate.

  4. Underlying tokens are transferred from the contract to the user.

  5. Vault shares (cTokens) are burned.

  6. Position adjustments are made if necessary.

Collateral Management Flow

  1. User calls postCollateral() to use cTokens as borrowing collateral.

  2. Contract verifies the asset is eligible as collateral and under the global cap.

  3. MarketManager is notified of the new collateral position.

  4. User's cTokens are marked as collateral and transfer-restricted.

  5. Colalteral 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. Protocol fees are extracted according to fee schedule.

  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: Harvested rewards are not immediately reflected in vault assets. Instead, rewards vest linearly over a defined period (typically 24 hours).

  • 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: Assets posted as collateral cannot be transferred until removed from collateral status.

  • Pause Mechanisms: Deposit, withdrawal, and harvesting operations can be paused independently for emergency response.

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

  • Multi-Source Feeds: Integration with multiple price oracle networks for redundancy and accuracy.

  • Fallback Mechanisms: Secondary pricing methods activate if primary oracles fail or deviate significantly.

  • Staleness Protection: Automatic detection and handling of stale price data.

Considerations

Collateral Caps: Global limits restrict total exposure to any single asset type, managing protocol-wide risk.

Minimum duration requirements: Collateral must remain posted for at least 20 minutes to prevent certain attack vectors.

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

convertToShares()

Contract: BaseCToken.sol

Description: Converts an amount of underlying assets to equivalent cToken shares using the current exchange rate.

Function signature:

function convertToShares(uint256 amount) public view returns (uint256)
Type
Name
Description

uint256

amount

The number of underlying tokens to theoretically convert to eTokens

Return data:

Type
Description

uint256

The number of cTokens a user would receive for the given amount of underlying


convertToAssets()

Contract: BaseCToken.sol

Description: Converts an amount of cToken shares to equivalent underlying assets using the current exchange rate.

Function signature:

function convertToAssets(uint256 tokens) public view returns (uint256) 
Type
Name
Description

uint256

tokens

The number of eToken shares to theoretically convert to underlying assets

Return data:

Type
Description

uint256

The number of underlying assets the user would receive for redeeming shares


Deposits

deposit()

Contract: BaseCToken.sol

Description: Deposits assets into the vault and receives shares.

Function signature:

function deposit(uint256 assets, address receiver) external returns (uint256 shares)
Type
Name
Description

uint256

assets

The amount of underlying assets to deposit.

address

receiver

The account that should receive the eToken shares.

Return data:

Type
Name
Description

uint256

shares

The amount of cToken shares received by receiver.

Events:

// From cToken
/// `keccak256(bytes("Deposit(address,address,uint256,uint256)"))`.
uint256 internal constant _DEPOSIT_EVENT_SIGNATURE =
    0xdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7;
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;

depositAsCollateral()

Contract: BaseCToken.sol

Description: Deposits assets and marks shares as collateral in one transaction.

Function signature:

function depositAsCollateral(
    uint256 assets, 
    address receiver) external returns (uint256 shares)
Type
Name
Description

uint256

assets

The amount of underlying assets to deposit.

uint256

receiver

The account that should receive the cToken shares.

Return Data:

Type
Name
Description

uint256

shares

The amount of cToken shares received by receiver.

Events:

// From cToken
/// `keccak256(bytes("Deposit(address,address,uint256,uint256)"))`.
uint256 internal constant _DEPOSIT_EVENT_SIGNATURE =
    0xdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7;
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
// 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.

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 permissions, the function will still deposit assets but won't post them as collateral.

Function signature:

function depositAsCollateralFor(
    uint256 assets,
    address receiver
) external nonReentrant returns (uint256 shares);
Type
Name
Description

uint256

assets

The amount of underlying assets to deposit.

uint256

receiver

The account that should receive the cToken shares.

Return Data:

Type
Name
Description

uint256

shares

The amount of cToken shares received by receiver.

Events:

// From cToken
/// `keccak256(bytes("Deposit(address,address,uint256,uint256)"))`.
uint256 internal constant _DEPOSIT_EVENT_SIGNATURE =
    0xdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7;
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
// From MarketManager
event PositionUpdated(address cToken, address account, bool open);


mint()

Contract: BaseCToken.sol

Description: Users deposit underlying assets into the market and receive cTokens in return.

Function signature:

function mint(uint256 amount) external returns (uint256)
Type
Name
Description

uint256

amount

The amount of the underlying asset to deposit

Return Data:

Type
Description

uint256

The amount of cTokens minted

Events:

// Emitted from cToken and underlying asset.
event Transfer(address indexed from, address indexed to, uint256 value);

mintFor()

Contract: BaseCToken.sol

Description: Deposits underlying assets into the market, and recipient receives cTokens.

Function signature:

function mintFor(uint256 amount, address recipient) external returns (uint256)
Type
Name
Description

uint256

amount

The amount of the underlying asset to deposit

address

recipient

The account that should receive the cTokens

Return data:

Type
Description

uint256

The amount of cToken minted

Events:

// Emitted from cToken and underlying asset.
event Transfer(address indexed from, address indexed to, uint256 value);

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.

Function signature:

    function withdraw(
        uint256 assets,
        address receiver,
        address owner
    ) public override nonReentrant returns (uint256 shares) {
Type
Name
Description

uint256

assets

The amount of underlying assets to withdraw.

address

receiver

The account that should receive the assets.

address

owner

The account that will burn their shares to withdraw assets.

Return data:

Type
Name
Description

uint256

shares

The amount of shares that were burned.

Events:

// From cToken
/// @dev `keccak256(bytes("Withdraw(address,address,address,uint256,uint256)"))`.
uint256 internal constant _WITHDRAW_EVENT_SIGNATURE =
    0xfbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db;
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
// From MarketManager
event PositionUpdated(address cToken, address account, bool open);

redeem()

Function: Burns shares to receive assets.

Contract: BaseCToken.sol

Function signature:

function redeem(
    uint256 shares, 
    address receiver, 
    address owner) external returns (uint256 assets)
Type
Name
Description

uint256

shares

The amount of shares to redeem.

address

receiver

The account that should receive the assets.

address

owner

The account that will burn their shares to receive assets.

Return data:

Type
Name
Description

uint256

assets

The amount of underlying assets sent to the receiver.

Events:

// From cToken
/// @dev `keccak256(bytes("Withdraw(address,address,address,uint256,uint256)"))`.
uint256 internal constant _WITHDRAW_EVENT_SIGNATURE =
    0xfbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db;
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
// From MarketManager
event PositionUpdated(address cToken, address account, bool open);

redeemFor()

Contract: BaseCToken.sol

Description: Used by a delegated user to redeem cToken in exchange for the underlying asset, on behalf of account.

Function signature:

function redeemFor(
    uint256 tokens, 
    address recipient, 
    address account) external returns (uint256)

Return data:

Type
Description

uint256

The amount of underlying asset redeemed

Events:

// Emitted from cToken and underlying asset.
event Transfer(address indexed from, address indexed to, uint256 value);
// From MarketManager
// Only emitted if positions need to be closed
event PositionUpdated(address cToken, address account, bool open);

redeemCollateral()

Description: Redeems collateralized shares to receive assets.

Contract: BaseCToken.sol

Function signature:

function redeemCollateral(
    uint256 shares, 
    address receiver) external returns (uint256 assets)
Type
Name
Description

uint256

shares

The amount of collateralized shares to redeem.

address

receiver

The account that should receive the assets.

Return data:

Type
Name
Description

uint256

assets

The amount of underlying assets sent to the receiver.

Events:

// From cToken
/// @dev `keccak256(bytes("Withdraw(address,address,address,uint256,uint256)"))`.
uint256 internal constant _WITHDRAW_EVENT_SIGNATURE =
    0xfbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db;
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
// 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:

function redeemFor(
        uint256 shares,
        address receiver,
        address owner) public returns (uint256 assets)
Type
Name
Description

uint256

shares

The amount of collateralized shares to redeem.

address

receiver

The account that should receive the assets.

address

owner

The account that will burn their shares to withdraw assets.

Return data:

Type
Name
Description

uint256

assets

The amount of underlying assets sent to the receiver.

Events:

// From cToken
/// @dev `keccak256(bytes("Withdraw(address,address,address,uint256,uint256)"))`.
uint256 internal constant _WITHDRAW_EVENT_SIGNATURE =
    0xfbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db;
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
// 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:

function redeemCollateralFor(
    uint256 shares,
    address receiver, 
    address owner) external returns (uint256 assets)
Type
Name
Description

uint256

shares

The amount of collateralized shares to redeem.

address

receiver

The account that should receive the assets.

address

owner

The account that owns the shares being redeemed.

Return data:

Type
Name
Description

uint256

assets

The amount of underlying assets sent to the receiver.

Events:

// From cToken
/// @dev `keccak256(bytes("Withdraw(address,address,address,uint256,uint256)"))`.
uint256 internal constant _WITHDRAW_EVENT_SIGNATURE =
    0xfbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db;
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
    0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
// From MarketManager
// Always emitted because collateral is being removed.
event CollateralUpdated(uint256 shares, bool increased, address account);

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:

function exchangeRate() external view returns (uint256)

Return data:

Type
Description

uint256

Exchange rate from shares to assets in WAD format


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:

function exchangeRateUpdated() external returns (uint256)

Return data:

Type
Description

uint256

Exchange rate from shares to assets in WAD format.


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: BaseCTokenWithYield.sol

Function signature:

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:

Interest accrues automatically when users interact with the protocol (mint, redeem, borrow, repay) as these functions call accrueIfNeeded internally.


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

function harvest(bytes calldata data) external returns (uint256 yield)
Parameter
Description

data

Encoded swap parameters to convert rewards to underlying assets.

Getting Vault Status

function getYieldInformation() external view returns (
    uint256 vestingRate,
    uint256 vestingEnd,
    uint256 lastVestingClaim,
    uint256 debtIndex
);

Returns current information about the vault's yield distribution:

  • vestingRate: Percentage rate at which yield is being distributed.

  • vestingEnd: When the current vesting period ends.

  • lastVestingClaim: Last time pending vesting rewards were claimed.

  • debtIndex: The current market debt index for interest calculations.

Considerations

  • Collateral Caps: Restricts the total exogenous risk from any single asset.

  • 20-Minute Minimum Duration: Collateral must be posted for at least 20 minutes.

  • Safe Functions: Enhanced protection against reentrancy and other vulnerabilities.

  • Liquidation Safeguards: Only authorized contracts can seize collateral during liquidations.

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:

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:

function deposit(uint256 amount, bool willLend) external
Type
Name
Description

uint256

amount

The amount of underlying token to be deposited.

bool

willLend

Whether the deposited underlying tokens should be lent out inside Curvance Protocol.

Events:

// From UniversalBalance
event Deposit(
    address indexed by,
    address indexed owner,
    uint256 assets,
    bool lendingDeposit
)
// 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:

function depositFor(uint256 amount, bool willLend, address recipient) external
Type
Name
Description

uint256

amount

The amount of underlying token to be deposited.

bool

willLend

Whether the deposited underlying tokens should be lent out inside Curvance Protocol.

address

recipient

The account who will receive the deposit.

Events:

// From UniversalBalance
event Deposit(
    address indexed by,
    address indexed owner,
    uint256 assets,
    bool lendingDeposit
)
// 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:

function multiDepositFor(
    uint256 depositSum,
    uint256[] calldata amounts,
    bool[] calldata willLend,
    address[] calldata recipients
) external
Type
Name
Description

uint256

depositSum

The total sum of underlying tokens being deposited.

uint256[]

amounts

An array containing the amount of underlying token to be deposited to each account.

bool[]

willLend

An array containing whether the deposited underlying tokens should be lent out inside Curvance Protocol for each account.

address[]

recipients

An array containing the accounts who will receive a deposit based on their matching amounts value.

Events:

For each deposit:

// From UniversalBalance
event Deposit(
    address indexed by,
    address indexed owner,
    uint256 assets,
    bool lendingDeposit
)
// 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:

function withdraw(
    uint256 amount,
    bool forceLentRedemption,
    address recipient
) external returns (uint256 amountWithdrawn, bool lendingBalanceUsed)
Type
Name
Description

uint256

amount

The amount of underlying token to be withdrawn.

bool

forceLentRedemption

Whether the withdrawn underlying tokens should be pulled only from owner's lent position or the full account.

address

recipient

The account who will receive the underlying assets.

Return data:

Type
Description

uint256

The amount of underlying token actually withdrawn.

bool

Whether lent balance was used for the withdrawal.

Events:

// From UniversalBalance
event Withdraw(
    address indexed by,
    address indexed to,
    address indexed owner,
    uint256 assets,
    bool lendingRedemption
)
// 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);
// From MarketManager
// Potentially emitted if positions need to be closed.
event PositionUpdated(address cToken, address account, bool open);

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:

function withdrawFor(
    uint256 amount,
    bool forceLentRedemption,
    address recipient,
    address owner
) external returns (uint256 amountWithdrawn, bool lendingBalanceUsed)
Type
Name
Description

uint256

amount

The amount of underlying token to be withdrawn.

bool

forceLentRedemption

Whether the withdrawn underlying tokens should be pulled only from owner's lent position or the full account.

address

recipient

The account who will receive the underlying assets.

address

owner

The account that will redeem from their universal balance.

Return data:

Type
Description

uint256

The amount of underlying token actually withdrawn.

bool

Whether lent balance was used for the withdrawal..

Events:

event Withdraw(
    address indexed by,
    address indexed to,
    address indexed owner,
    uint256 assets,
    bool lendingRedemption
)
// 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);
// From MarketManager
// Potentially emitted if positions need to be closed.
event PositionUpdated(address cToken, address account, bool open);

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:

function multiWithdrawFor(
    uint256[] calldata amounts,
    bool[] calldata forceLentRedemption,
    address recipient,
    address[] calldata owners
) external
Type
Name
Description

uint256[]

amounts

An array containing the amount of underlying token to be withdrawn from each account.

bool[]

forceLentRedemption

An array containing whether the withdrawn underlying tokens should be pulled only from an owners lent position or the full account.

address

recipient

The account who will receive the underlying assets.

address[]

owners

An array containing the accounts that will redeem from their Universal Balance.

Events:

For each withdrawal:

event Withdraw(
    address indexed by,
    address indexed to,
    address indexed owner,
    uint256 assets,
    bool lendingRedemption
)
// 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);
// From MarketManager
// Potentially emitted if positions need to be closed.
event PositionUpdated(address cToken, address account, bool open);

shiftBalance()

Description: Moves a user's Universal Balance between lent and sitting mode.

Contract: UniversalBalance.sol

Function signature:

function shiftBalance(
    uint256 amount,
    bool fromLent
) external returns (uint256 amountWithdrawn, bool lendingBalanceUsed)
Type
Name
Description

uint256

amount

The amount of underlying token to be shifted.

bool

fromLent

Whether the shifted underlying tokens should be pulled from the user's lent balance or the sitting balance.

Return data:

Type
Description

uint256

The amount of underlying token actually shifted

bool

Whether lent balance was used for the operation

Events:

// 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
)
// 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:

function transfer(
    uint256 amount,
    bool forceLentRedemption,
    bool willLend,
    address recipient
) external returns (uint256 amountTransferred, bool lendingBalanceUsed)
Type
Name
Description

uint256

amount

The amount of underlying token to be transferred.

bool

forceLentRedemption

Whether the transferred underlying tokens should be pulled only from the caller's lent position or the full account.

bool

willLend

Whether the deposited underlying tokens should be lent out inside Curvance Protocol.

address

recipient

The account who will receive the transferred balance.

Return data:

Type
Description

uint256

The amount of underlying token actually transferred.

bool

Whether lent balance was used for the transfer.

Events:

// 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
)
// 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:

function transferFor(
    uint256 amount,
    bool forceLentRedemption,
    bool willLend,
    address recipient,
    address owner
) external returns (uint256 amountTransferred, bool lendingBalanceUsed)
Type
Name
Description

uint256

amount

The amount of underlying token to be transferred.

bool

forceLentRedemption

Whether the transferred underlying tokens should be pulled only from owner's lent position or the full account.

bool

willLend

Whether the deposited underlying tokens should be lent out inside Curvance Protocol.

address

recipient

The account who will receive the transferred balance.

address

owner

The account that will transfer from their universal balance.

Return data:

Type
Description

uint256

The amount of underlying token actually transferred.

bool

Whether lent balance was used for the transfer.

Events:

// 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
)
// 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:

function depositNative(bool isLent) external payable
Type
Name
Description

bool

isLent

Whether the deposited native tokens should be lent out inside Curvance Protocol (as wrapped native).

Events:

// 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);
// 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:

function depositNativeFor(
    bool isLent,
    address recipient
) external payable
Type
Name
Description

bool

isLent

Whether the deposited native tokens should be lent out inside Curvance Protocol (as wrapped native).

address

recipient

The account who will receive the deposit.

Events:

// 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);
// 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:

function multiDepositNativeFor(
    uint256[] calldata amounts,
    bool[] calldata willLend,
    address[] calldata recipients
) external payable
Type
Name
Description

uint256[]

amounts

An array containing the amount of native token to be deposited to each account.

bool[]

willLend

An array containing whether the deposited native tokens should be lent out inside Curvance Protocol for each account.

address[]

recipients

An array containing the accounts who will receive a deposit based on their matching amounts value.

Events:

// 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);
// 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:

function withdrawNative(
    uint256 amount,
    bool forceLentRedemption,
    address recipient
) external returns (uint256 amountWithdrawn, bool lendingBalanceUsed)
Type
Name
Description

uint256

amount

The amount of native token to be withdrawn.

bool

forceLentRedemption

Whether the withdrawn underlying tokens should be pulled only from owner's lent position or the full account.

address

recipient

The account who will receive the underlying assets.

Return data:

Type
Description

uint256

The amount of native token actually withdrawn.

bool

Whether lent balance was used for the withdrawal.

Events:

// From UniversalBalanceNative
event Withdraw(
    address indexed by,
    address indexed to,
    address indexed owner,
    uint256 assets,
    bool lendingRedemption
)
// From the wrapped native asset
event Withdrawal(address user, uint256 amount);
// 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);
// 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:

function withdrawNativeFor(
    uint256 amount,
    bool forceLentRedemption,
    address recipient,
    address owner
) external returns (uint256 amountWithdrawn, bool lendingBalanceUsed)
Type
Name
Description

uint256

amount

The amount of native token to be withdrawn.

bool

forceLentRedemption

Whether the withdrawn underlying tokens should be pulled only from owner's lent position or the full account.

address

recipient

The account who will receive the native token.

address

owner

The account that will redeem from their universal balance.

Return data:

Type
Description

uint256

The amount of native token actually withdrawn.

bool

Whether lent balance was used for the withdrawal.

Events:

// From UniversalBalanceNative
event Withdraw(
    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);
// 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);
// 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:

function multiWithdrawNativeFor(
    uint256[] calldata amounts,
    bool[] calldata forceLentRedemption,
    address recipient,
    address[] calldata owners
) external
Type
Name
Description

uint256[]

amounts

An array containing the amount of native token to be withdrawn from each account.

bool[]

forceLentRedemption

An array containing whether the withdrawn underlying tokens should be pulled only from an owners lent position or the full account.

address

recipient

The account who will receive the native assets.

address[]

owners

An array containing the accounts that will redeem from their Universal Balance.

Events:

For every withdrawal:

// From UniversalBalanceNative
event Withdraw(
    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);
// 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);
// From MarketManager
// Potentially emitted if positions need to be closed.
event PositionUpdated(address cToken, address account, bool open);