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...
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...
For capital that works, not capital that waits.
Curvance makes DeFi lending more efficient, plain and simple. By enhancing the interaction between borrowers and lenders regarding collateral, liquidity, and yield, Curvance provides an alternative to legacy lending protocols that have stagnated in innovation.
Most lending protocols force users to choose between earning active yield or borrowing against their assets. Curvance is designed to remove that tradeoff while setting a new standard for capital efficiency.
Borrow Against Yield-Bearing Collateral Users can supply assets, such as LSTs, LRTs, Stablecoins, or LP tokens, and continue earning yield throughout DeFi while using them as collateral.
Industry-Leading LTV Ratios Borrow up to 97% against select assets like USDC, WETH, and WBTC, with a liquidation engine designed for all market conditions.
Native Looping and Leveraging Boost exposure to assets and strategies with native one-click looping. One click to rule them all.
Dynamic Liquidation Framework Utilizes orderflow auctions to recapture MEV from liquidation and dynamic penalties based on market conditions, enabling Curvance to adapt to any situation.
Plugin System Developers can build directly on Curvance with simple modules that plug into existing infrastructure. No permission required. No rewrites needed.
Curvance offers a modular, security-first lending protocol that supports the full lifecycle of productive on-chain capital. Instead of treating yield-bearing assets as edge cases, they’re the standard.
It’s not just an upgrade in interface or user experience. The foundation itself is built differently, designed for flexibility, adaptability, and sustained use across ecosystems.
This is lending built for scale.
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 in the world. 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/
Telegram: https://t.me/curvance
Discord: https://discord.com/invite/curvance
The Curvance protocol offers a new solution to the challenge of capital efficiency and composability in DeFi. Designed from the ground up, the protocol enables users to interact with various DeFi ecosystems and strategies while prioritizing security, capital efficiency, and ease of use. This design enhances the user experience and unlocks new opportunities for yield maximization and enhanced financial flexibility.
Curvance’s code architecture employs a novel, risk-isolated design of multiple markets derived from various DeFi ecosystems. This allows users to participate in markets that are comprised of underlying assets from protocols and ecosystems such as Aerodrome, Pendle, Eigenlayer LRTs, and Ethena. Users can select markets that align with their risk preference, creating a spectrum of options from conservative, low-risk exposure to more dynamic, higher-yield opportunities.
This model strikes a flexible balance between the traditional shared pool and fully isolated pool models utilized by incumbent protocols, improving capital efficiency and protocol security. Each market’s risk exposure is managed through dynamically adjustable collateral caps and bad debt socialization, effectively reducing protocol risk. This approach allows for the development of exotic markets, offering unique yield opportunities not present in traditional markets.
One of the most sought-after goals in DeFi is composability, which allows protocols and applications to interact seamlessly, improving user experience and maximizing capital efficiency.
The Curvance protocol delivers on the vision of composability by directly hooking up to applications and infrastructural technology. The protocol also optimizes DeFi participation for the benefit of users and builders alike due to its ability to natively route vault liquidity through all applicable reward layers.
For users, this creates fundamentally new DeFi actions, such as using liquidity provided on a DEX as liquidity in Curvance's lending markets or the ability to borrow capital and bridge across networks in a single click.
For builders, this fixes issues with token incentives by creating the ability to reward users for various actions from any chain on any chain. Full liquidity mining programs can be built permissionlessly on top of Curvance for any supported asset on any supported chain.
Example: A user deposits their USDC/AERO Aerodrome LP tokens into the Curvance protocol. Leveraging the ERC-4626 architecture, the protocol automatically routes the deposited LP tokens back to the Aerodrome platform and compounds the rewards, capturing both the underlying reward layers and Curvance’s native rewards.
This structure enables the user to collect all yield layers seamlessly within the Curvance platform and benefit from simplified position management while unlocking collateralization opportunities.
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.
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.
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.
Minimizing systemic risk
Collateral caps are used as a core safeguard for the lending markets, setting specific restrictions on the amount of each asset that can be used as collateral. This mechanism is essential for protecting the protocol against bad debt and unintentionally incentivizing market manipulation by bad actors.
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.
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.
Shortly after the Curvance DAO launches, the Curvance Collective will vote to select their preferred third-party risk management group. This group will be tasked with determining risk parameters for all supported assets. Together, the Curvance DAO and the elected group will regularly review and adjust collateral caps in response to changes in offside liquidity, ensuring alignment with on-chain liquidity. This dynamic approach to risk management helps mitigate systemic risks and maintain stability within the protocol's lending markets.
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.
By carefully linking collateral caps to liquidity dynamics and adjusting them via DAO governance, the protocol can provide a robust, stable approach to collateralized borrowing. This method aligns user security with broader protocol health, allowing users to scale responsibly within DeFi.
There is a structured process for integrating new assets, ensuring each addition is carefully evaluated, secure, and beneficial to the ecosystem. This multi-step approach involves collaboration with asset teams and Curvance DAO contributors, due diligence on infrastructure, and rigorous testing, leading to seamless integration within the protocol.
1. Initial Conversation
The integration process begins with an initial conversation, typically initiated through business development efforts or inbound requests from teams looking to have their assets supported on Curvance. These discussions aim to understand the asset’s potential and explore collaborative opportunities.
2. Infrastructure and Compatibility Assessment
Both the assigned Curvance development team and the asset's team verify the availability of critical infrastructure, such as price oracles and other relevant data feeds. This assessment helps determine the type of integration possible, factoring in considerations such as:
Total available liquidity for the asset
Whether other lending protocols support the same asset
The quality and reliability of infrastructure components
3. Strategy Vault Development
Based on the information gathered, the teams decide on the optimal type of integration for the asset. This decision balances factors such as liquidity, support within the broader DeFi ecosystem, and the asset's potential for yield generation and capital efficiency within the Curvance platform.
The Curvance DAO's assigned engineering team develops a strategy vault tailored for the new asset. This vault is designed to integrate with the existing protocol infrastructure while maximizing yield and liquidity opportunities specific to the asset.
4. Security Audits and Validation
The newly developed strategy undergoes rigorous third-party security audits to ensure protocol stability and user safety. This step is crucial for maintaining trust in the protocol's security standards and protecting the broader DeFi community.
5. Asset Integration and Deployment
Once the strategy passes the security audit, the new asset is integrated into the Curvance protocol. From this point, users can access and interact with the asset throughout the platform, leveraging its unique yield opportunities, lending capabilities, and liquidity options.
This structured approach ensures that each new asset added to Curvance meets rigorous security, liquidity, and usability standards, creating a seamless user experience and maintaining the protocol’s integrity.
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.
Credit lines for depositors inside Curvance
A user’s borrowing capacity within Curvance is determined by two key factors: the collateralization ratio set by the Curvance DAO and the available liquidity within 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 a collateralization ratio of 80%, the user can borrow up to $80 in assets, 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.
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 pTokens received at the time of the initial deposit.
The Curvance protocol provides new innovations in liquidity mining infrastructure by streamlining incentive programs for protocols, token issuers, and blockchains while simplifying participation for liquidity providers and users. Traditionally, setting up secondary and tertiary incentivization layers involves complex processes and additional steps for users, leading to a measurable drop in participation and adoption. Curvance’s infrastructure delivers incentives directly while optimizing the underlying liquidity mining strategies for greater efficiency and growth. Additionally, user liquidity can be routed through multiple layers of incentivization, ensuring peak capital efficiency and maximizing yield.
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.
Audit reports will be posted publicly when available.
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 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.
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.
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.
A public audit has been conducted through , 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.
Pricing assets inside Curvance
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.
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.
An RPC (Remote Procedure Call) allows users to interact with a blockchain by sending requests to remote servers (nodes) for actions like checking account balances, submitting transactions, or querying blockchain data. It simplifies blockchain interactions by enabling commands to be executed on external servers without requiring direct access to the blockchain infrastructure. This functionality powers wallets, decentralized applications, and other blockchain services, ensuring a seamless user experience.
However, testnet networks are often less stable than their mainnet counterparts. As a result, users may experience issues with certain RPC endpoints. To address these issues, refer to the attached guide for detailed steps on how to update your RPC endpoint and restore functionality.
What types of assets does Curvance support?
The surge in popularity of yield-bearing assets spans wide, from assets such as LSTs, LRTs, yield-bearing stablecoins, Perpetual Exchange/DEX LP tokens, and more. This growing asset class offers both fungibility and income-generating potential.
With Curvance, users can embrace a new paradigm that removes the compromise between participation in lending protocols and yield farming across DeFi. This enables individuals to explore new strategies, such as providing liquidity on Aerodrome while simultaneously borrowing against their LP tokens, maximizing both composability and capital efficiency.
The flow on Curvance will be as follows:
A user is interested in unlocking capital on their yield bearing asset. The Curvance protocol will facilitate the user's redirection of the supplied assets back into the respective underlying protocol to earn the native yield while permitting the user to borrow against their position as it remains productive.
Upon deposit, the user will receive representative pTokens (position tokens) minted from Curvance, equal to their pool share. These pTokens will redeem their share of deposits after the loan is paid back.
This unlocks the potential for users to leverage Curvance's advanced position looping, enabling participation in traditional strategies like LRT or yield-bearing stablecoin looping and more advanced strategies such as LP token looping.
Through advanced position looping, users can borrow against their LP tokens while continuing to earn the underlying yield. The borrowed funds are then zapped into the underlying protocol's LP token and redeposited into Curvance, effectively creating a leveraged yield farming position.
The Curvance protocol accommodates a wide range of assets, including non-yield-bearing tokens like WETH, WBTC, USDC, and USDT. These assets, though lacking native yield generation, still play a vital role in DeFi by providing liquidity and stability within lending markets.
With Curvance, users can unlock the capital potential of non-yield-bearing assets without sacrificing flexibility. Unlike traditional lending protocols, which limit non-yield-bearing assets to passive roles, Curvance integrates them into a composable framework that allows users to borrow, lend, and manage positions across multiple chains.
How It Works on Curvance:
A user can deposit non-yield-bearing assets such as WBTC into a market on Curvance, using the asset as collateral for leveraging their original exposure or accessing liquidity without losing their original exposure. While these assets do not generate native yield, users benefit from access to CVE emissions and protocol incentives, enhancing passive yield on their holdings.
Upon deposit, users receive pTokens, representing their share in the pool. These pTokens can be redeemed for the original collateral once any outstanding loan obligations are repaid.
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.
1/1 Approvals: Users can approve tokens for each individual transaction, providing maximum control and limiting risk.
Infinite Approvals: For convenience, users can approve tokens once for unlimited transactions with that asset, eliminating the need for repeated confirmations.
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.
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.
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.
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.
This documentation is a rough draft and currently undergoing legal review for compliance, and as a result, are subject to change.
If you would like your platform to display the amount of underlying tokens the user would receive for redeeming eTokens, you may use the convertToAsset()
function present in all eToken contracts using the following arguments:
Conversely, if you want to let the user choose how much underlying assets to withdraw, you may use the convertToShares()
function:
To withdraw USDC from eUSDC directly, you may use the redeem()
function present in all eToken contracts using the following arguments:
Below is a full implementation, letting the user choose how much underlying to redeem:
If your app requires users to withdraw underlying assets to another address, you can use the redeemFor()
function in the eToken contract using the following function arguments:
Calling redeemFor():
Curvance offers a Universal Balance system that provides a simplified way to manage withdrawals by calling withdraw()
in the UniversalBalance contract using the following function arguments:
Implementation snippet:
For native gas tokens, Curvance provides a specialized Universal Balance Native contract:
Alternatively, if your app requires depositing for another address, you may use the withdrawFor()
and withdrawNativeFor()
functions in their respective contracts.
The Curvance Protocol operates on a biweekly (2-week) epoch cycle for distributing rewards point amounts across all supported chains. This process is coordinated by the MessagingHub contract which serves as the cross-chain communication layer.
Key aspects:
Each epoch lasts exactly 2 weeks as defined by EPOCH_DURATION
in the CentralRegistry.
The MessagingHub coordinates epoch transitions by querying points across all chains.
The RewardManager tracks which epoch's rewards have been delivered using nextEpochToDeliver
.
State transitions occur when the MessagingHub calls recordEpochRewards()
with the calculated reward amounts.
State machine:
Rewards are distributed proportionally across all chains based on the amount of points on each chain.
Key aspects:
The MessagingHub uses Wormhole's Cross-Chain Query (CCQ) to get accurate point counts from all chains.
Reward distribution is calculated as: (Chain's Points / Total Points) * Total Rewards
.
Each chain receives a pro-rata share of the total protocol fees based on its proportion of points.
This creates an incentive for chains to accumulate points to increase their share of rewards.
Data flow:
The system allows users to accumulate rewards over multiple epochs without having to claim them immediately.
Key aspects:
Each user has a userNextClaimIndex
that tracks the next epoch they can claim rewards from.
epochRewardsPerPoint
records the rewards allocated per point for each epoch.
Users can claim multiple epochs of accumulated rewards in a single transaction.
The formula for a user's rewards for an epoch is: (User's Points * epochRewardsPerPoint[epoch]) / WAD
.
The cross-chain movement of rewards (primarily USDC) is handled through Circle's Cross-Chain Transfer Protocol (CCTP) and Wormhole's messaging system.
Key aspects:
The protocol uses CCTP to transfer USDC between chains, ensuring secure token transfers.
Wormhole's automatic relayer system delivers cross-chain messages with execution guarantees.
The MessagingHub sends both USDC tokens via CCTP and reward metadata via Wormhole.
When a chain receives USDC, it routes the tokens to its local RewardManager.
Cross-chain token flow:
This architecture ensures that rewards are efficiently distributed across chains while maintaining a consistent state of reward records throughout the entire protocol.
Empower applications, chains, payment systems, and more to monetize idle capital.
Curvance’s Universal Balance offers protocols a seamless way to integrate Curvance's yield into their products. The Universal Balance smart contracts enable protocols to route platform assets—such as USDC, USDT, or ETH—into the supply side of Curvance's low-risk money markets. This allows their native users to benefit from an additional passive yield stream without directly interacting with Curvance.
With a single integration, protocols benefit from:
Enhanced yield is achieved by routing supported assets into Curvance’s lending markets, unlocking an additional source of yield for the protocol and/or its users.
Accessible liquidity by allowing assets to remain available for immediate use.
Universal Balance seamlessly integrates into existing vaults and liquidity pools without changing their structure.
Simplified UX with one-click integrations that work across multiple chains.
Universal Balance automates passive earning while maintaining complete flexibility. Instead of leaving assets unproductive, funds are routed to yield-generating markets without requiring active management. There are no lockups, and users retain full control over their balances, ensuring capital is always available when needed.
Security is a core focus of Curvance, and Universal Balance is designed to minimize risk exposure while maximizing yield efficiency:
Risk-Isolated Markets: Assets are only routed into audited, risk-adjusted lending pools.
Dual-Oracles & Circuit Breakers: Every asset in Universal Balance is protected by to prevent manipulation.
Non-Custodial & Permissionless: Universal Balance never takes custody of user funds, ensuring assets remain accessible at all times.
Universal Balance is plug-and-play and requires minimal setup. With smart contract hooks and plugin compatibility, protocols can optimize how their assets flow through Curvance. Its modular design allows for effortless integration with DeFi protocols, automated strategies, and governance systems make it a scalable solution for builders across the ecosystem.
A Telegram trading bot integrates with Curvance’s Universal Account Balance, allowing users to earn passive yield on idle balances while maintaining full trading flexibility. The development team configures a risk profile that aligns with their strategy, giving users the option to opt in or out of yield generation.
Users experience no change in their normal trading interactions—idle funds are automatically earning yield until needed for execution. When a trade is initiated, the bot instantly pulls liquidity from Universal Balance, ensuring seamless transactions without delays. This integration enhances capital efficiency without disrupting the user experience.
uint256
tokens
The number of eToken shares to theoretically convert to underlying assets
uint256
amount
The number of underlying tokens to theoretically convert to eTokens
uint256
tokens
The number of eTokens to redeem for underlying tokens
address
recipient
The account who will receive the underlying assets
// Get the eUSDC contract with signer
const eUSDC = 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
const USDCWithdrawAmountFormatted = ethers.utils.parseUnits(userInputAmount, 6);
// Convert the underlying amount to shares
const eUSDCSharesToRedeem = await eUSDC.convertToShares(USDCWithdrawAmountFormatted);
// Redeem eUSDC for USDC
const underlyingRedeemed = await eUSDC.redeem(depositAmount);
uint256
amount
The amount of the eToken shares to redeem.
address
recipient
The account that should receive the underlying assets.
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.
const universalBalance = new ethers.Contract(
UNIVERSAL_BALANCE_ADDRESS,
UNIVERSAL_BALANCE_ABI,
signer
);
// Withdraw from Universal Balance.
// withdrawAmount = unit formatted amount of lent asset (eg. BigInt(1000000000)).
// using true, to withdraw from the lent balance only, instead of the sitting balance.
// using recipient as the recipient address, can be the user's address or any address.
await universalBalance.withdraw(withdrawAmount, true, recipient);
const universalBalanceNative = new ethers.Contract(
UNIVERSAL_BALANCE_NATIVE_ADDRESS,
UNIVERSAL_BALANCE_NATIVE_ABI,
signer
);
// Withdraw from Universal Balance Native.
// withdrawAmount = Amount of native assets in wei.
// using true, to withdraw from the lent balance only, instead of the sitting balance.
// using recipient as the recipient address, can be the user's address or any address.
await universalBalance.withdrawNative(withdrawAmount, true, recipient);
The Curvance protocol enables users to earn yield by supplying assets to borrowers in its peer-to-peer lending markets. Depending on the market, most deposits can be used as collateral, allowing users the ability to borrow against their holdings. Assets supplied solely for lending, however, cannot be borrowed against.
Common Borrowable Assets:
Stablecoins: Stablecoins are commonly borrowed by users who want to improve their net DeFi strategy yields.
Volatile Non-Yield-Bearing Tokens: Volatile Non-Yield-Bearing Assets are commonly borrowed by users who want to create cross-token strategies such as longing BTCETH or farming staked ether yield.
Adding New Supported Tokens: New tokens can be introduced as lending options by the Curvance DAO, expanding opportunities.
When users deposit tokens into Curvance as lenders, they receive a proportionate amount of eTokens (earn tokens), representing their share in the lending pool. Earned interest is automatically compounded, increasing the user’s overall position over time.
Instant Yield Access: Users can deposit into earning positions for no cost and immediately earn yield on their assets.
Automatically Reinvested Yield: Interest on outstanding debt is managed by automation across all positions for all users simultaneously, continuously reinvesting accrued interest back into all user's positions.
Instant Liquidity Access: After a 20-minute cooldown period, lent liquidity can be redeemed at any time unless there is 100% utilization in that particular market.
Curvance offers users on the demand side various options to generate yield and access liquidity by borrowing against their deposited assets. Deposited assets are routed to all supported yield opportunities. Users can then use them as collateral to access new liquidity.
Common Depositable Assets:
Interest-Bearing Stablecoins: Stablecoins are commonly borrowed by users who want to improve their net DeFi strategy yields.
Liquid Staked/Restaked Tokens: Assets such as LSTs are natively supported, offering streamlined yield generation.
LP Tokens: More complex assets, such as LP tokens for AMMs, Perps, CLOBs, etc., benefit from auto-compounding, optimized yield generation, and improved user experience.
Adding New Supported Tokens: New tokens can be introduced as deposit options by the Curvance DAO, expanding opportunities.
When users deposit tokens into Curvance, they receive a proportionate amount of pTokens (position tokens), representing their share in the managed vault. Earned yield is automatically compounded, increasing the user’s overall position over time.
Instant Yield Access: Users can deposit into earning positions for no cost and begin earning yield on their assets immediately.
Instant Liquidity Access: Deposited assets can, in most cases, be collateralized, allowing access to borrowed liquidity against the market value of deposited assets.
Automatically Reinvested Yield: Yield on deposited assets is managed by automation across all positions for all users simultaneously, continuously reinvesting accrued yield into all users' positions.
Economies of Scale: By pooling all users' positions together, any managed actions are executed for all users simultaneously, minimizing automated management costs. For example, If 10,000 users deposit into a particular asset vault, auto compounding operations are executed for all 10,000 positions at once, reducing user costs by (9999 / 10000) 99.99%.
To enable an asset as collateral, users can follow these steps:
Go to the main dashboard to view all deposited assets.
Locate the assets to be used as collateral.
Enable the desired assets as collateral using the "Increase Collateral" button.
Note: When enabled, the corresponding deposited assets continue to earn yield, ensuring efficient capital utilization across DeFi.
Compensating lenders for their liquidity inside 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.
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 (currently set to every 4 hours). If utilization drops below the target rate, the vertex multiplier lowers, leading to a decrease in rates to maintain stability.
Initial State: A lending pool contains 1,000,000 USDC, of which 800,000 USDC is borrowed, resulting in an 80% 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 100%, the interest rate rises significantly due to exceeding the 85% vertex point. In this scenario, rates increase to 8%, which then double every 4 hours 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.
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 eUSDC contract handles the debt issuance. The borrowed USDC is sent directly to your wallet.
To borrow, you may call the borrow()
function in the respective eToken contract using these function arguments:
uint256
amount
The amount of the underlying asset to borrow.
Implementation snippet:
async function borrowUSDC(amount) {
// USDC has 6 decimal places
const USDCDecimals = await USDC.decimals();
const amountInSmallestUnit = ethers.utils.parseUnits(amount.toString(), USDCDecimals);
// Borrow USDC
const borrowTx = await eUSDC.borrow(amountInSmallestUnit);
const receipt = await borrowTx.wait();
console.log(`Successfully borrowed ${amount} USDC`);
return receipt;
}
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.
When interacting with eUSDC contracts, you might encounter various errors. Curvance uses error selectors to provide specific information about what went wrong:
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 === '0x65513fc1') console.error('Invalid parameter provided');
else if (errorSelector === '0x37cf6ad5') console.error('Unauthorized access');
else if (errorSelector === '0x4f3013c5') console.error('Token not listed in this market');
else if (errorSelector === '0xf47323f4') console.error('Market is currently paused');
else console.error('Unknown error code:', errorSelector);
} else {
console.error('Error details:', error.message);
}
throw error;
}
}
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.
The Plugin System enables unparalleled interoperability and flexibility within the Curvance protocol, allowing users to authorize specific actions by external addresses on their behalf. This structure enhances the protocol’s composability and enables innovative use cases across DeFi, while maintaining user security and control.
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.
The Plugin System empowers users in several ways:
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.
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.
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.
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.
These quick start guides will help you integrate with Curvance's smart contracts using JavaScript and ethers.js 5.7. 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.
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.
To follow these guides, you'll need:
Node.js environment.
ethers.js v5.7 installed (npm install [email protected]).
Basic knowledge of JavaScript and Ethereum.
A wallet with testnet or mainnet funds (depending on your target network).
These quick start guides cover the following areas:
Atlas Fastlane Auctions: Learn how to participate in liquidation auctions through Curvance's MEV capture system.
Plugin Integration: Implement custom plugins and zappers to enable complex operations like multi-step positions, reward collection, and cross-protocol interactions.
Loans & Collateral: Deposit assets, post collateral, and manage positions using Curvance's pToken system.
Borrowing & Repayment: Borrow against your collateral, manage debt positions, and repay loans with the eToken system.
Leverage: Create leveraged positions efficiently using Curvance's native position folding and management tools.
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!
Once a user has approved your contract as a delegate, your application can perform various actions on their behalf.
Below is a list of contracts and their delegable actions:
GaugeManager
claim()
RewardManager
manageRewardsFor()
UniversalBalance
depositFor(), withdrawFor(), transferFor(), multiDepositFor(), multiWithdrawFor()
PositionManagement
leverageFor(), deleverageFor()
Ptoken
depositAsCollateralFor(), redeemCollateralFor(), redeemFor(),
Etoken
borrowFor(), redeemFor()
For those in a hurry...
What is Curvance? Curvance is a decentralized, multichain liquidity hub that empowers users to unlock the full potential of their digital assets.
Key Features:
Secure, modular, and composable protocol supporting any ERC-20 token
Chain-agnostic reward and utility layer for yield-bearing assets
ERC-4626 vault technology for integration with third-party strategies and DeFi flywheels
Multichain equivalence powered by Wormhole for access to opportunities across ecosystems
Custom-built liquidation engine and cross-chain voting for an improved user experience
Benefits:
Unlock liquidity and maximize yields across multiple chains
Simplify DeFi with a seamless, trustless user experience
Participate in a sustainable DAO model with decentralized governance
Discover new yield-generating opportunities and innovative DeFi products
Join the Curvance Community: Learn more about Curvance and get involved in shaping the future of DeFi. Visit the website, X, Telegram, and Discord channels to stay updated and join the conversation.
Website: https://curvance.com/
Telegram: https://t.me/curvance
Discord: https://discord.com/invite/curvance
1. Does Curvance expose users to bridge risk?
No, Curvance does not expose users to bridge risk in lending positions or vault strategies. Vault deposits and lending positions remain on their respective chains, and users are never forced to bridge assets. Bridging via Wormhole is utilized when a user uses a crosschain plugin to migrate liquidity across chains.
2. How does Curvance optimize yield compared to other DeFi protocols?
Curvance auto-compounds yield-bearing assets and routes liquidity through additional reward layers, ensuring maximum yield efficiency. Unlike traditional lending protocols, Curvance allows collateralized assets to continue earning while being used within DeFi strategies.
3. What makes Curvance’s lending markets unique?
Curvance features risk-isolated, intent-based lending markets, blending elements of shared pools and isolated markets. This enables flexible risk management, higher LTVs for safer assets, and innovative lending opportunities tailored to different DeFi strategies.
4. How does Universal Balance benefit protocols and users?
Universal Balance lets protocols passively earn a yield on idle assets without disrupting the user experience. Users benefit by earning passive rewards while retaining full control over their funds. While protocols can integrate native yield generation with minimal friction.
5. How does the Plugin System improve composability?
Curvance’s Plugin System allows developers to extend protocol functionality with custom integrations, automated strategies, and third-party applications. It enhances composability by enabling seamless yield routing, lending automation, and governance tools—all within Curvance.
6. What makes Curvance’s vote-escrow system different?
Curvance’s multichain vote-escrow system removes liquidity fragmentation present in all other traditional vote-escrow models. Allowing native token lockers to direct platform emissions to vaults on any supported chain. Protocol fees are distributed pro-rata across all chains, ensuring efficient capital flow.
7. Is Curvance non-custodial?
Yes. Curvance is fully non-custodial, meaning users always maintain control of their assets. Funds are never held by Curvance, and users interact with permissionless smart contracts for deposits, lending, and withdrawals.
8. How does Curvance handle liquidations?
Curvance leverages MEV-optimized liquidations to minimize costs for borrowers while ensuring market stability. With dynamic liquidation incentives and order flow auctions (OFAs), Curvance enhances efficiency and reduces slippage for liquidated positions.
9. What security measures are in place?
Curvance prioritizes security with:
Dual-oracle protection to prevent price manipulation.
Circuit breakers to halt abnormal market activity detected by the Dual Oracle system.
Third-party audits from leading smart contract security firms such as: Spearbit, Cantina, Trail of Bits, Trust Security, and yAudit.
Collateral caps and risk-adjusted lending pools to mitigate systemic risks.
10. What types of assets does Curvance support?
Curvance supports both yield-bearing and non-yield-bearing assets, including LSTs, LRTs, stablecoins, LP tokens, and popular assets like WETH and WBTC. Yield-bearing assets continue generating rewards even when used as collateral, while non-yielding assets benefit from enhanced capital efficiency through lending, borrowing, and liquidity incentives.
The Curvance platform enables other protocols to run incentive campaigns tailored to their specific objectives. By utilizing Curvance, protocols can enhance existing liquidity mining and product growth strategies while introducing new utility and maximizing yield opportunities for their users. This is accomplished by:
Optimization of DEX Liquidity Mining: The Curvance protocol optimizes DEX liquidity mining for other protocols by creating vaults that support their DEX liquidity pool tokens. These vaults natively auto-compound underlying emissions and incentives back into LP tokens. This leads to deeper, continually growing DEX liquidity for protocols and higher yields for liquidity providers on Curvance.
Multichain Incentivization: Traditional vote escrow models require protocols to lock their tokens and commit to a specific blockchain ecosystem for long periods. With the Multichain Gauge System, protocols can leverage veCVE to create a sustainable incentivization strategy that spans multiple chains and DEXs, providing enhanced flexibility and enabling cross-chain growth.
Ease of Incentivization: Protocols can seamlessly stream any whitelisted ERC-20 token through the Partner Gauge System. This includes ecosystem grants and the protocol's native token, offering a flexible approach to incentivization.
Asset Looping and Leveraging: Protocols can incentivize users to leverage their positions through one-click asset looping. This allows users to leverage basic yield-bearing assets and long-tail exotic assets, such as Decentralized Exchange LP tokens and Perpetual Exchange LP tokens, unlocking a new market of liquidity providers for protocols to capitalize on.
Deleveraging is the process of reducing your position's leverage by withdrawing collateral and repaying debt. This can be done in two ways:
Partial deleveraging: Reduce leverage while maintaining an active position.
Full deleveraging: Completely unwind the position by repaying all debt.
Before deleveraging, you need to:
Understand your current position: Review your collateral amount, debt amount, and health factor.
Set an appropriate slippage tolerance: Typically 0.5-2% depending on asset volatility.
Calculate the optimal deleveraging amounts: Determine how much collateral to withdraw and debt to repay.
Deleveraging requires constructing a DeleverageStruct that specifies how to unwind your position:
With the DeleverageStruct prepared, execute the deleverage operation:
Swap Data Configuration:
The swapData
array can contain multiple swaps for complex routes.
Each swap must specify the correct input/output tokens and amounts.
Slippage tolerance should be set appropriately for each swap.
Amount Calculations:
collateralAmount
: The amount of position tokens to withdraw.
repayAmount
: The amount of debt to repay in the borrowed asset.
Ensure these amounts are properly calculated to maintain desired position size.
Protocol-Specific Features:
Some protocols may require additional data in the auxData
field.
Check protocol documentation for specific requirements.
Consider using protocol-specific optimizations when available.
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.
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.
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.
Curvance employs a dual token model:
Position Tokens (pTokens): Collateral tokens that can be posted as security for borrowing.
Debt Tokens (eTokens): Tokens that can be borrowed against posted pToken collateral.
Both are collectively referred to as Market Tokens (mTokens), with each serving a specific purpose within the ecosystem.
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.
Deposit: User deposits underlying assets into a pToken.
Collateral Posting: User posts pToken shares as collateral.
Borrowing: User borrows eTokens against posted collateral.
Repayment: User repays borrowed eTokens.
Withdrawal: User withdraws collateral after repaying debt.
Unlike many lending protocols, Curvance disables rehypothecation of position token deposits. This means:
Collateral assets cannot be re-lent to other users.
Each asset's risk exposure remains isolated.
Risk modeling becomes more accurate and predictable.
Complex collateral chains that could amplify systemic risk are avoided.
The collateral system has several key components:
Collateral Caps: Each pToken has a maximum amount of shares that can be posted as collateral, limiting exogenous risk exposure.
Collateral Posting: Assets are posted as shares, allowing collateral caps to grow proportionally with any yield-generating strategies.
Cap Management: Collateral caps can be decreased even if current utilization is above the new cap, which prevents new risk while not forcing position unwinding.
The Dynamic Liquidation Engine enables more nuanced position management:
Healthy Position: Collateral value exceeds required thresholds.
Soft Liquidation Threshold: When collateral/debt ratio falls below soft threshold, partial liquidations begin with base penalties.
Hard Liquidation Threshold: When ratio falls below hard threshold, complete liquidation is permitted with higher penalties.
Bad Debt Threshold: When debt exceeds collateral value, socialized bad debt handling begins.
Curvance features a next-generation liquidation auction system, OEV (Optimal Extractable Value) that maximizes MEV capture:
High-Performance Batch Processing:
Enables concurrent processing of multiple liquidations within a single transaction.
Dramatically reduces latency and gas costs while maximizing throughput efficiency.
Ensures rapid position resolution during market stress.
Off-chain Auction Architecture:
Conducts competitive bidding in a gas-efficient off-chain environment.
Sophisticated bidding algorithms rank each proposal based on multiple parameters including close factor and penalty fees.
Enables optimal price discovery while minimizing on-chain footprint.
MEV capture:
Transforms traditional MEV extraction into protocol-captured value.
Eliminates wasteful gas wars through structured auction mechanisms.
Provides a more efficient and equitable liquidation process.
Curvance enhances risk modeling through:
Asset-Specific Risk Parameters: Each asset has customized collateralization requirements.
Three-Tier Liquidation System: Soft, hard, and bad debt thresholds for graduated liquidation responses.
Volatility-Responsive Liquidations: Aggressive liquidations in volatile periods, gentler in stable periods.
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.
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.
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:
Events:
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:
Return data:
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:
Events:
When depositing into the AeroSTETH_ETHPToken, you're providing Aerodrome stETH-ETH LP tokens that will automatically earn yields through Aerodrome. Here's how to do it:
First, check the user's balance and ensure they have enough LP tokens:
Then approve and make the deposit by calling deposit()
in the pToken contract, or depositAsCollateral()
to both deposit and post as collateral all in one transaction.
Function arguments when:
calling deposit():
calling depositAsCollateral():
Alternatively, if your app requires users depositing for another address, you can use depositAsCollateralFor()
.
When you deposit LP tokens into this yield-optimized pToken:
Your Aerodrome stETH-ETH LP tokens are transferred to the pToken contract.
The pToken automatically stakes these LP tokens in Aerodrome.
The staked position begins earning AERO rewards.
When harvested, these rewards are swapped to ETH and STETH and then deposited into to the Aerodrome pool to mint new LP tokens, which are then staked back in Aerodrome gauge.
This increases the total assets of the pToken, vesting until the next harvest, benefiting all depositors proportionally.
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.
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.
The Plugin Architecture is built around three primary components:
ActionRegistry: Base library that manages user configuration for delegation and transfer permissions
PluginDelegable: Abstract contract that implements delegate approval functionality
Central Registry: Core hub that inherits from ActionRegistry and serves as the source of truth
Each user has a configuration record in the ActionRegistry that tracks:
This state record facilitates two key security mechanisms:
Transfer locking: Controls whether a user's tokens can be transferred.
Delegation control: Controls whether a user can approve new delegates.
Delegations are tracked in a nested mapping structure:
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.
Approval Index Mechanism
The approval index serves as a master revocation system. When a user increments their approval index:
All previously approved delegates are instantly revoked..
New delegations must be established at the new index
Transfer & Delegation Cooldown
The system implements protective cooldown periods:
Disabled → Enabled: When a user re-enables transfers or delegation capability, a cooldown period applies before the action takes effect.
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.
Contracts that integrate with the Plugin Architecture:
Inherit from PluginDelegable.
Implement permission checks using _checkDelegate() for delegate-initiated operations.
Reference the Central Registry for user configuration state.
The architecture is utilized by core protocol components including token contracts (pTokens, eTokens) and position management systems, allowing for complex operations like automated liquidation protection, cross-chain rebalancing, and advanced trading strategies.
Who is Curvance designed for, and how do those people benefit from it's existence?
The Curvance protocol empowers a wide range of DeFi users, each with specific needs. By delivering tailored solutions to each customer type, the platform drives growth, capital efficiency, and revenue generation through diverse mechanisms.
Why They Use Curvance: Retail investors seek accessible, simplified ways to maximize yields and manage their assets across multiple chains.
How Curvance Benefits Them:
Unified position management tooling for an easy, seamless DeFi experience.
Access to optimized yield strategies, enhancing yield on deposits.
Collateralized loans and one-click leverage that improves financial flexibility.
Visibility into top opportunities across chains, supporting well-informed investment decisions.
Why They Use Curvance: Token issuers and Treasury Managers seek to maximize treasury performance, liquidity management efficiency, and liquidity depth for their tokens.
How Curvance Benefits Them:
Auto-compounding of LP tokens to optimize yields on protocol-owned assets and flywheel bribing.
Increased ROI via the CVE Gauge System, stacking on top of yield from underlying strategies.
Access to collateralized loans to enhance treasury efficiency.
Flexible veCVE tokens adaptable to existing flywheels, providing additional flexibility when expanding to new networks.
Why They Use Curvance: Institutional investors can use the Curvance platform for custom-built strategies, capital efficiency for their existing liquidity provisioning deals, and peace of mind with permissioned pools, which align with their overall compliance requirements.
How Curvance Benefits Them:
Access to customized structured products tailored to institutional needs.
Compliance infrastructure integrations ensure that strategies align with regulatory standards.
Access to spot leverage maximizing investment power and potential returns.
Enhanced capital efficiency via optimized strategies designed for high liquidity.
Why They Use Curvance: Chains and networks can leverage the platform to drive ecosystem growth, increase TVL (total value locked), and foster a vibrant DeFi environment.
How Curvance Benefits Them:
Predictable, measurable TVL inflows, bolstering network activity and liquidity.
Enhanced transaction volumes, promoting sequencer revenue.
Marketing and ecosystem exposure, amplifying their reach and user base.
A multichain vote-escrow system enables networks to "siphon" liquidity from other chains.
Capture MEV created by Curvance Protocol
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.
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:
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.
Rapid Execution: The entire auction process takes just 300 milliseconds (3/10ths of a second), ensuring liquidations occur quickly and with minimal delay.
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.
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.
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.
Curvance brand assets can be found .
// 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 data
const deleverageData = {
positionToken: P_TOKEN,
collateralAmount: collateralAmount,
borrowToken: E_TOKEN,
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}`);
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
);
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
}
owner => approvalIndex => delegate => isApproved
const lpToken = new ethers.Contract(ADDRESSES.STETH_ETH_LP, ERC20_ABI, provider);
const balance = await lpToken.balanceOf(userAddress);
const depositAmount = ethers.utils.parseEther("10"); // 10 LP tokens
if (balance.lt(depositAmount)) {
throw new Error("Insufficient LP 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 pToken to spend LP tokens
await lpToken.approve(ADDRESSES.AERO_STETH_ETH_PTOKEN, depositAmount);
// Get the pToken contract
const pToken = new ethers.Contract(
ADDRESSES.AERO_STETH_ETH_PTOKEN,
PTOKEN_ABI,
signer
);
// Deposit as collateral or regular deposit
if (asCollateral) {
await pToken.depositAsCollateral(depositAmount);
} else {
await pToken.deposit(depositAmount, userAddress);
}
To deposit USDC into eUSDC directly, you may use the mint()
function present in all eToken contracts using the following arguments:
uint256
amount
The amount in shares to.
Below is a full implementation:
// Get the eUSDC contract with signer
const eUSDC = new ethers.Contract(ADDRESSES.EUSDC, ETOKEN_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.EUSDC, depositAmount);
// Deposit USDC for lending
await eUSDC.mint(depositAmount);
When you deposit USDC into eUSDC:
Your USDC tokens are transferred to the eToken contract.
You receive eUSDC tokens representing your lending position.
Your USDC becomes available for borrowers to borrow (subject to their collateral).
As borrowers pay interest, the exchange rate between eUSDC and USDC increases.
When you redeem your eUSDC tokens later, you receive your original USDC plus accrued interest.
If your app requires users to mint pTokens to another contract, you can use the mintFor()
function in the eToken contract using the following function arguments:
Calling mintFor():
uint256
amount
The amount of the underlying asset to deposit.
address
recipient
The account that should receive the eTokens.
Curvance offers a Universal Balance system that provides a simplified way to manage deposits by calling deposit()
in the UniversalBalance contract using the following function arguments:
uint256
amount
The amount of underlying token to be deposited.
bool
willLend
Whether the deposited underlying tokens should be lent out inside Curvance Protocol.
Implementation snippet:
const universalBalance = new ethers.Contract(
UNIVERSAL_BALANCE_ADDRESS,
UNIVERSAL_BALANCE_ABI,
signer
);
// Approve Universal Balance to spend USDC
await usdc.approve(UNIVERSAL_BALANCE_ADDRESS, depositAmount);
// Deposit to Universal Balance
// If willLend is true, funds are deposited into eUSDC
// If willLend is false, funds are held in the Universal Balance without being lent
await universalBalance.deposit(depositAmount, willLend);
For native gas tokens, Curvance provides a specialized Universal Balance Native contract:
const universalBalanceNative = new ethers.Contract(
UNIVERSAL_BALANCE_NATIVE_ADDRESS,
UNIVERSAL_BALANCE_NATIVE_ABI,
signer
);
// Deposit ETH - if isLent is true, ETH is wrapped and lent as WETH
await universalBalanceNative.depositNative(isLent, {
value: ethers.utils.parseEther("1.0") // 1 ETH
});
Alternatively, if your app requires depositing for another address, you may use the depositFor()
and depositNativeFor()
functions in their respective contracts.
If you have many users to loan assets for, you may use the multiDepositFor()
function which deposits underlying token into recipients Universal Balance accounts, either to be held or lent out.
multiDepositFor() can be called with the following arguments:
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.
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.
The system identifies positions where collateral value is insufficient to cover debt:
Collateral Value < (Debt * Liquidation Incentive) = Bad Debt Condition
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.
When a liquidator executes the bad debt liquidation:
The system calculates total debt to be closed for the specific asset.
Determines how much can be repaid via the liquidator's token transfer.
Calculates the remainder as bad debt to be socialized.
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.
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.
The socialization mechanism operates through a coordinated interaction between:
Debt Calculation: For each liquidation, the system:
Calculates the total debt to be closed.
Determines how much can be repaid through liquidation transfers.
Subtracts this from total debt to find the bad debt amount.
Efficient Processing:
Multiple liquidations can be batched in a single transaction.
Gas optimization by consolidating repayments and bad debt calculations.
eToken Integration:
The eToken contract recognizes unpaid debt by adjusting totalBorrows
.
This maintains the exchange rate mechanism while distributing losses.
Atlas Integration:
Atlas prioritizes liquidations to minimize bad debt through efficient market mechanisms.
Buffer system ensures Atlas gets priority for executing liquidations.
Dynamic penalty system incentivizes liquidators appropriately based on market conditions.
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.
In Curvance's lending ecosystem, eTokens represent debt positions. Each eToken corresponds to a specific underlying asset - for example, eUSDC represents borrowed USDC. When you borrow an asset from Curvance, you're interacting with an eToken contract that tracks your debt.
eUSDC is the debt token representing borrowed USDC in Curvance. When you borrow USDC, you're effectively taking on eUSDC 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 eUSDC 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.
Before interacting with eUSDC, you'll need to set up your environment with ethers.js v5.7.3. You'll need contract ABIs for interaction, but we'll keep it simple here - you can obtain the full ABIs from Curvance documentation.
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 Curvance documentation
const eUSDCAddress = '0x...'; // eUSDC contract address
const USDCAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'; // USDC address
// Initialize contract instances with appropriate ABIs
const eUSDC = new ethers.Contract(eUSDCAddress, ETokenABI, wallet);
const USDC = new ethers.Contract(USDCAddress, USDCABI, wallet);
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 debtBalanceWithUpdateSafe
function fetches your current debt balance with the latest interest applied:
async function checkDebtBalance() {
const debtBalance = await eUSDC.debtBalanceWithUpdateSafe(wallet.address);
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.
The interest rate for eUSDC debt depends on market supply and demand. You can check current market conditions to make informed borrowing decisions:
async function checkEUSDCMarketInfo() {
// Get the current exchange rate (how much USDC each unit of eUSDC is worth)
const exchangeRate = await eUSDC.exchangeRateWithUpdateSafe();
console.log(`Current exchange rate: ${ethers.utils.formatUnits(exchangeRate, 18)}`); // In WAD format
// Get total outstanding borrows in the market
const totalBorrows = await eUSDC.totalBorrowsWithUpdateSafe();
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.
When borrowing through eUSDC, 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.
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:
Understand your target position: Determine the collateral asset, borrow asset, and desired leverage ratio.
Set an appropriate slippage tolerance: Typically 0.5-2% depending on asset volatility.
Approve the necessary contracts: Your collateral token must approve the Position Management contract.
Here's how to prepare the deposit and leverage transaction:
// 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);
Constructing the LeverageStruct
The key to a successful leverage operation is properly constructing the LeverageStruct
:
// 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 leverage struct
const leverageData = {
borrowToken: E_TOKEN,
borrowAmount: ethers.utils.parseUnits('500', 18),
positionToken: P_TOKEN,
swapData: swapData,
auxData: '0x' // Optional auxiliary data for specialized protocols
};
Executing the Leverage Operation
With the LeverageStruct
prepared, execute the leverage operation:
// 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}`);
If you already have collateral deposited, you can increase your leverage without an additional deposit:
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}`);
Position Health and Risk Management:
Monitor your position's health factor before and after leveraging.
Consider the maximum leverage ratio allowed by the protocol.
Be aware of liquidation risks when increasing leverage.
Calculate the minimum collateral required to maintain a safe position.
Swap Data Configuration:
The swapData
must accurately reflect the desired swap route.
Ensure the inputAmount
matches the borrowAmount
in the leverage struct.
Set appropriate slippage tolerance for the swap (typically 0.3-1% for stable pairs, 1-3% for volatile pairs).
Amount Calculations:
Ensure these amounts are properly calculated to achieve desired leverage ratio.
Consider protocol fees when calculating amounts.
Protocol-Specific Features:
Some protocols may require additional data in the auxData
field.
Check protocol documentation for specific requirements.
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 the value of a borrower’s collateral falls below a defined threshold, known as the Health Factor.
The Health Factor measures an account’s stability and capacity to cover borrowed funds. Calculated as:
A Health Factor above 1 signifies sufficient collateral value and a safe position.
A Health Factor below 1 indicates insufficient collateral value, triggering Curvance’s liquidation processes.
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.
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.
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.
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.
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.
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.
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
struct LeverageStruct {
IEToken borrowToken;
uint256 borrowAmount;
IPToken positionToken;
SwapperLib.Swap swapData;
bytes auxData;
}
Encapsulates all data needed for a leverage operation, including which token to borrow, how much to borrow, and which position token to leverage against.
struct DeleverageStruct {
IPToken positionToken;
uint256 collateralShares;
IEToken borrowToken;
SwapperLib.Swap[] swapData;
uint256 repayAmount;
bytes auxData;
}
Contains data for deleverage operations, specifying which collateral to liquidate and how to route funds to repay debt.
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.
PTokens: Position tokens that serve as collateral.
ETokens: 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.
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.
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.
The FeeManager serves as the central collection point for protocol fees across the Curvance ecosystem. It acts as a unified hub for aggregating fees generated from various protocol operations.
Protocol Revenue Streams: Fees collected from lending/borrowing operations, liquidations, and other protocol services flow into the FeeManager.
Reward Token Registry: The contract maintains a registry of valid reward tokens that can be accepted as fees, enabling flexibility in fee collection.
Storage Architecture:
rewardTokens: Array of supported token addresses.
rewardTokenInfo: Mapping tracking token status (including OTC eligibility).
Before cross-chain distribution, collected fees may need transformation into the protocol's primary fee token.
Permissioned Swap Execution: Authorized harvesters can initiate swaps through the multiSwap function.
OTC Protection: Tokens marked for OTC are protected from automated swaps.
Token Verification: Ensures input tokens are registered reward tokens and output is the fee token.
Price Oracle Integration: Uses Oracle Manager to determine fair market value for OTC transactions.
Token Earmarking: Tokens can be marked as "for OTC" (value=2), preventing them from being swapped.
Price Protection: Includes slippage control and deadline validation to ensure fair execution.
The cross-chain distribution of fees is coordinated between the FeeManager and MessagingHub.
MessagingHub Pull: The MessagingHub calls pullFees() to retrieve the collected fee tokens
Fee Allocation:
Compounding fees are allocated to DAO address for harvester bot operations.
Remaining fees are sent to MessagingHub for cross-chain distribution.
Cross-Chain Transfer:
MessagingHub uses Circle's CCTP (Cross-Chain Transfer Protocol) and Wormhole for secure cross-chain messaging.
Tokens are distributed proportionally based on points across chains.
Epoch Management:
Rewards are distributed during epoch executions based on points on each chain.
Wormhole's Cross-Chain Query (CCQ) gathers data about locked tokens across chains.
Circle CCTP: Used for transferring fee tokens across chains.
Wormhole: Provides the messaging infrastructure for cross-chain communications.
Wormhole CCQ: Enables querying of lock points data from other chains.
FeeManager leverages external liquidity sources to optimize token swaps.
Offchain Solvers: Integration with 1inch and potentially other solvers for optimized swap routing
Swap Safety:
SwapperLib.swapSafe()
enforces validation rules.
External calldata checker verifies swap transactions.
Token approvals are managed automatically.
Permissioned Access: Only authorized harvester addresses can execute swaps.
Swap Verification:
Validates input/output tokens match expected values.
Strict swap parameter validation prevents malicious transactions.
The FeeManager's cross-chain architecture enables Curvance to maintain a unified fee management system across multiple blockchains, ensuring proportional reward distribution and efficient token handling throughout the protocol ecosystem.
The Curvance Protocol implements a sophisticated cross-chain architecture that enables seamless operation across multiple blockchains while maintaining protocol-wide consistency. This architecture enables unified governance, shared incentives, and the efficient distribution of rewards across the entire ecosystem.
The cross-chain system consists of several specialized components that work together:
CentralRegistry: Serves as a single source of truth for each chain, containing protocol configuration and contract addresses.
MessagingHub: The unified communication layer that enables cross-chain message passing. It handles:
Epoch coordination
Fee distribution
Governance message propagation
FeeManager: Collects protocol fees and prepares them for cross-chain distribution.
RewardManager: Distributes rewards to entitled parties.
VotingHub: Coordinates token emission allocation across all chains based on governance decisions.
Curvance's cross-chain architecture relies on two primary technologies:
Wormhole
Facilitates generic message passing between chains.
Provides Cross-Chain Query (CCQ) capability for state verification.
Powers the system's VAA (Verifiable Action Approval) verification.
Circle's CCTP (Cross-Chain Transfer Protocol)
Handles token transfers between chains.
Primarily used for fee and reward token movements.
The Curvance Protocol has several critical cross-chain data flows:
This diagram illustrates the bi-weekly process of distributing protocol fees across all chains in the Curvance ecosystem:
A privileged operator initiates the process on one chain by querying points across all other chains using Wormhole CCQ.
After verification of chain data, the system pulls accumulated fees from the local FeeManager.
The protocol calculates each chain's share of fees based on the proportion of total points on that chain.
Fees are then transferred to destination chains using CCTP for the token transfer, alongside a Wormhole message carrying distribution instructions.
Each receiving chain's MessagingHub routes the received tokens to its RewardManager for distribution to point accumulators proportionally.
This mechanism ensures fair fee distribution based on governance participation across all chains in the ecosystem.
The Curvance cross-chain architecture implements several key states:
This diagram outlines the state transitions during a Curvance protocol epoch:
Epoch Start: A new epoch begins based on the timestamp defined by genesisEpoch + (epochNumber * EPOCH_DURATION)
.
Fee Collection & Trading Activity: During the epoch, fees accumulate from protocol activity across all chains independently.
Cross-Chain Coordination: Near epoch end, the protocol aggregates points across all chains using Wormhole CCQ to determine reward distribution.
Fee Distribution: Collected fees are distributed pro-rata to each chain based on its proportion of total points, with tokens bridged using CCTP.
Epoch Completion: The nextEpochToDeliver
counter is incremented, allowing the system to track epochs sequentially and ensure proper distribution.
The epoch system operates in a strictly sequential manner to ensure consistency across chains.
The cross-chain architecture implements several safety mechanisms:
Message Hash Verification: Each cross-chain message is tracked by its hash to prevent replay attacks.
Messaging Status Controls: The messaging hub can be put into various states to control message creation and execution:
Status 1: Full operation (create and execute messages).
Status 2: Execute-only mode (no new messages).
Status 3: Complete pause (no operations).
Guardian Validation: Uses Wormhole guardians to validate cross-chain messages through VAA signatures.
Chain Verification: Messages are only accepted from known, registered chains configured in the Central Registry.
Fallback Mechanisms: If the system gets stuck, administrative overrides exist to ensure continued operation.
The Curvance Protocol maintains cross-chain consistency through:
Synchronized Epochs: All chains share a common epoch schedule, defined by the genesisEpoch and epochDuration values.
Cross-Chain Queries: The system uses Wormhole's CCQ to verify the state of remote chains before making critical decisions.
Sequential Epoch Processing: Each epoch must be processed in order, and all chains must catch up sequentially (tracked via nextEpochToDeliver).
Verifiable Messages: All cross-chain communications are verified through cryptographic signatures from Wormhole guardians.
The Curvance cross-chain architecture creates a unified, multi-chain DeFi protocol that maintains consistency and shared state across all supported networks. This design enables protocol-wide coordination for token emissions, fee distribution, and governance while allowing users to interact with the protocol on their preferred blockchain.
When withdrawing from pTokens, you have a few choices of functions to call depending on the needs of your platform.
withdraw()
- If you would like to input the amount of underlying shares to withdraw, burning pTokens that are not being used as collateral.
redeem()
- If you would like to redeem pTokens, that are currently being yield optimized but also not being used as collateral.
redeemCollateral()
- If you would like to redeem pTokens that are currently being used as collateral.
redeemFor()
- If you would like to redeem pTokens that are not being used as collateral, on behalf of a user. Requires the user to delegate your platform.
redeemCollateralFor()
- If you would like to redeem pTokens that are currently being used as collateral, on behalf of a user. Requires the user to delegate your platform.
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.
When calling withdraw, you may call it with the following arguments:
Code snippets:
When calling redeem, you may use the following arguments:
Code snippet:
when calling redeemCollateral, you may use the following arguments:
Code snippet:
To significantly reduce the number of transactions required, consider using our delegation system. Utilize redeemFor()
and redeemCollateralFor()
. You can follow our detailed .
This guide demonstrates how to integrate with Curvance Protocol using JavaScript and ethers.js v5.7, with specific examples using the Convex stETH-ETH pool for collateral and USDC for lending.
Curvance has two primary token types that together make up the protocol's market infrastructure:
pTokens (Position Tokens): Used for collateral positions. These are ERC4626-compliant vault tokens that represent a user's collateral. Many pTokens are yield-optimized, automatically compounding underlying yields for depositors.
eTokens (Earn Tokens): Used for lending positions. These tokens represent a user's deposits that are being lent out.
mTokens (Market Tokens): The collective term for both pTokens and eTokens.
Yield-optimized pTokens like ConvexSTETH_ETH2PoolPToken provide automatic yield generation for depositors:
Deposit Process: Users deposit Curve stETH-ETH LP tokens into the pToken.
Behind the Scenes: The pToken stakes these LP tokens in Convex Finance.
Yield Generation: The staked position earns CRV and CVX rewards from Convex.
Harvesting: Periodically, these rewards are harvested, swapped to ETH, and used to mint more LP tokens.
Compounding: New LP tokens are staked back in Convex, increasing the total assets.
User Benefit: The value of users' shares increases over time as yields accumulate.
This automated yield optimization happens regardless of whether the position is being used as collateral.
First, install ethers.js v5.7:
You'll need to define your contract addresses and ABIs. Here's a simplified example for the most important ones:
Before sending any transactions, check these important protocol values by calling mintPaused()
in the MarketManager contract.
Protocol Status: Verify if minting of the pToken is not paused:
Call mintPaused()
with the following arguments. Returning a 0 or 1 if not paused, else indicating that minting is paused.
Implementation:
Collateral Caps: Check if the asset has reached its collateral cap by calling collateralCaps()
, and collateralPosted()
in the MarketManager contract:
Calling collateralCaps()
using these function arguments, returning uint256 indicating the maximum amounts of pTokens that can be posted as collateral:
Calling collateralPosted()
using these function arguments, returning uint256 indicating the current amount of pTokens currently posted as collateral:
Implementation:
Minimum Hold Period: Be aware that Curvance implements a 20-minute minimum hold period for deposits to mitigate flash loan attacks.
Yield Optimization: For yield-optimized pTokens, understand that your share value increases over time rather than the number of shares.
Error Handling: Implement proper error handling for all transactions.
Gas Estimation: Use estimateGas before sending transactions to ensure proper gas limits.
Approval Checking: Check existing allowances before sending approval transactions.
Transaction Monitoring: Implement logic to track transaction status after submission.
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.
When you're ready to reduce or eliminate your debt, you can repay it through the eUSDC contract. Repayment requires you to first approve the eUSDC contract to spend your USDC, then call the repay()
function with the following function arguments:
Implementation snippet:
Curvance makes full repayment convenient by allowing you to pass 0 as the amount, which automatically repays your entire outstanding debt. This saves you from having to calculate the exact debt amount with accrued interest.
A unique feature of Curvance is the ability to repay debt on behalf of another address. 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:
Implementation snippets:
This function allows anyone to repay debt for any user without requiring permission from the borrower, creating interesting possibilities for social coordination in DeFi.
uint256
amount
The amount of the underlying asset to repay, or 0 for the full outstanding amount.
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;
if (isFullRepay) {
// Get current debt balance for full repayment
approvalAmount = await eUSDC.debtBalanceCached(wallet.address);
// Add 1% buffer for accrued interest
approvalAmount = approvalAmount.mul(101).div(100);
}
await USDC.approve(eUSDCAddress, approvalAmount);
// Repay the debt
const repayTx = await eUSDC.repay(amountInSmallestUnit);
const receipt = await repayTx.wait();
console.log(`Repaid ${isFullRepay ? 'full debt' : amount + ' USDC'}`);
return receipt;
}
address
account
The account address to repay on behalf of.
uint256
amount
The amount to repay, or 0 for the full outstanding amount.
async function repayForAccount(account, amount) {
const USDCDecimals = await USDC.decimals();
const amountInSmallestUnit = ethers.utils.parseUnits(amount.toString(), USDCDecimals);
// Approve USDC to be spent by eUSDC 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;
}
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.
// Get the pToken contract with signer
const pToken = new ethers.Contract(
ADDRESSES.AERO_STETH_ETH_PTOKEN,
PTOKEN_ABI,
signer
);
// 1 Aerodrome stETH/ETH LP token
const assetsToWithdraw = ethers.utils.parseUnits("1",18);
// OPTIONAL: If you want to move tokens as a third party without delegation you can have
// the user approve your platform to spend their pTokens.
pToken.approve(YOUR_PLATFORM_ADDRESS, amountToWithdraw);
// call withdraw
const assetsWithdrawn = await pToken.withdraw(assetsToWithdraw, receiverAddress, owner);
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.
// Get the pToken contract with signer
const pToken = new ethers.Contract(
ADDRESSES.AERO_STETH_ETH_PTOKEN,
PTOKEN_ABI,
signer
);
// 1 Curvance AeroSTETH_ETHPToken
const sharesToWithdraw = ethers.utils.parseUnits("1", 18);
// OPTIONAL: If you want to move tokens as a third party without delegation you can have
// the user approve your platform to spend their pTokens.
pToken.approve(YOUR_PLATFORM_ADDRESS, sharesToWithdraw);
// Call redeem
pToken.redeem(pTokensToWithdraw, YOUR_PLATFORM_ADDRESS, sharesToWithdraw);
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.
// Get the pToken contract with signer
const pToken = new ethers.Contract(
ADDRESSES.AERO_STETH_ETH_PTOKEN,
PTOKEN_ABI,
signer
);
// 1 Curvance AeroSTETH_ETHPToken
const sharesToWithdraw = ethers.utils.parseUnits("1", 18);
// OPTIONAL: If you want to move tokens as a third party without delegation you can have
// the user approve your platform to spend their pTokens.
pToken.approve(YOUR_PLATFORM_ADDRESS, sharesToWithdraw);
// Call redeem
pToken.redeemCollateral(pTokensToWithdraw, YOUR_PLATFORM_ADDRESS, sharesToWithdraw);
npm install [email protected]
const ADDRESSES = {
STETH_ETH_LP: "0x21E27a5E5513D6e65C4f830167390997aA84843a",
USDC: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
CONVEX_STETH_ETH_PTOKEN: "0x..." // Replace with actual pToken address
EUSDC: "0x..." // Replace with actual eToken address
};
address
Address of the pToken to check the minting status of.
const marketManager = new ethers.Contract(MARKET_MANAGER_ADDRESS, MARKET_MANAGER_ABI, provider);
const isPaused = await marketManager.mintPaused(CONVEX_STETH_ETH_PTOKEN);
address
Address of the pToken to check the max amount of shares that can be posted as collateral.
address
Address of the pToken to check the amount of collateral posted.
const cap = await marketManager.collateralCaps(PTOKEN_ADDRESS);
const posted = await marketManager.collateralPosted(PTOKEN_ADDRESS)
const capReached = posted.gte(cap) && !cap.isZero();
The Messaging Hub serves as the central communication layer of the Curvance Protocol, facilitating cross-chain operations between all supported blockchains. It coordinates epoch transitions, distributes protocol fees, manages token emissions, and facilitates the migration of user positions between chains.
The Messaging Hub operates in four states:
Inactive (0): Not used (reverts if attempted).
Send Active (1): Can send messages but not receive them.
Fully Active (2): Can send and receive messages.
Emergency Paused (3): All cross-chain messaging functionality is disabled.
State transitions require appropriate permissions:
Standard DAO permissions can pause the system.
Elevated permissions are required to unpause or modify messaging pathways.
The Messaging Hub defines a type system for cross-chain messages:
1
Basic fee transfer between chains.
2
Gauge emission configuration.
3
Epoch reward distribution.
The Messaging Hub aggregates points across all chains using Wormhole's Cross-Chain Query (CCQ) system. This data drives protocol-wide reward distribution based on the proportional vote-escrow locking across the entire ecosystem.
Protocol fees are collected by each chain's FeeManager and transferred via CCTP, with cross-chain coordination handled by Wormhole messaging. This enables a unified reward system where all fees contribute to protocol-wide incentives.
Token emissions are determined by governance voting and distributed to each chain through the Messaging Hub. This ensures that incentives align with governance decisions across the entire protocol.
The Messaging Hub manages native gas tokens to pay for cross-chain messaging fees across the entire protocol.
The contract holds native gas tokens to pay for cross-chain message delivery.
Gas cost depends on destination chain, payload size, and gas limit configuration.
Default gas limit (300,000) ensures sufficient resources on destination chains.
Gas fees are calculated through quoteMessageFee
which includes:
Wormhole relayer fees for message delivery.
Additional Wormhole core message publishing fees.
This architecture allows the protocol to operate seamlessly across multiple chains while maintaining security, proper fee distribution, and efficient gas usage for all cross-chain operations.
The Messaging Hub acts as an abstraction layer over multiple underlying cross-chain communication systems:
Wormhole handles general message passing and validation.
Circle's CCTP manages cross-chain stablecoin transfers.
Message Keys link CCTP transfers with Wormhole messages.
The Messaging Hub implements multiple security mechanisms:
Message Deduplication: All message hashes are recorded to prevent replay attacks.
Source Validation: Messages are only processed from known messaging hubs on authorized chains.
Payload Type Validation: Each payload type requires specific validation logic
Chain ID Validation: Messages are validated against registered chain IDs..
Status Controls: The messaging status state machine prevents undesired message processing
The architecture includes specialized handling for cross-chain synchronization issues:
When a RewardManager is offline, rewards are sent to the DAO address.
If epoch progression is out of sync, the system routes funds to maintain protocol health.
Native gas tokens stored in the Messaging Hub ensure cross-chain actions can be funded.
This resilient design ensures the protocol can maintain operations even when individual chains or components experience temporary issues.
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.
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.
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.
Users need to call the setDelegateApproval()
function on the appropriate Curvance contract.
Calling setDelegateApproval()
is called correctly with these arguments:
Below is an example showing how to implement this using Ethers.js 5.7 for a position that uses PositionManagementSimple
(such as pwstETH):
When implementing delegation in your application:
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:
Handle revocation: Users can revoke delegation at any time, so design your application to gracefully handle this case.
Transparent permissions: Clearly communicate to users which actions your application will perform on their behalf.
Gas optimization: Consider batching multiple delegated actions when possible to reduce gas costs.
After implementing the delegation setup, your application should:
Verify the delegation was successful.
Store the user's delegation status in your application state.
Implement the specific delegated actions your application needs.
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.
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.
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 MarketManager.sol
and LiquidationManager.sol
, you'll find integrated explanations of liquidation workflows that span multiple contracts.
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.
eToken
When users deposit assets into Curvance as lenders, they receive a proportionate amount of eTokens (earn tokens), 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.
pToken
When users deposit tokens into the Curvance Protocol, they receive a proportionate amount of pTokens (position 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.
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 pwstETHPositionManagementContract = new ethers.Contract(
pwstETH_PositionManagement_Address,
pwstETH_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;
}
}
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.
To understand leveraging in Curvance, you need to be familiar with two key data structures that control the leverage and deleverage operations:
struct LeverageStruct {
IEToken borrowToken; // The eToken you want to borrow from
uint256 borrowAmount; // Amount of underlying tokens to borrow
IPToken positionToken; // The pToken to deposit borrowed funds into
SwapperLib.Swap swapData; // Instructions for swapping borrowed tokens
bytes auxData; // Optional protocol-specific data
}
struct DeleverageStruct {
IPToken positionToken; // The pToken you're unwinding
uint256 collateralAmount; // Amount of pTokens to redeem
IEToken borrowToken; // The eToken debt to repay
SwapperLib.Swap[] swapData; // Array of swaps to execute
uint256 repayAmount; // Amount of debt to repay
bytes auxData; // Optional protocol-specific data
}
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-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 leverageData = {
borrowToken: eDAIAddress,
borrowAmount: ethers.utils.parseUnits(amountForLeverage.toString(), 18),
positionToken: pPendlePTAddress,
auxData: null,
swapData: swapData
};
// Encode the data
leverageData.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
]
);
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 P_TOKEN = '0x...'; // Your collateral token
const E_TOKEN = '0x...'; // Your borrow token
// Initialize contracts
const marketManager = new ethers.Contract(
MARKET_MANAGER,
['function statusOf(address) view returns (uint256, uint256, uint256)'],
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
);
Maintaining a healthy leverage ratio is crucial to avoid liquidation. Regularly check your position's health by using liquidationStatusOf()
in the MarketManager contract. The function returns the lFactor
and current prices for the specified tokens.
Function arguments:
address
account
The account to check liquidation status for.
address
earnToken
The eToken (debt token) to be repaid during potential liquidation.
address
positionToken
The pToken (collateral token) to be seized during potential liquidation.
Which returns a tuple:
uint256
lFactor - Account's current liquidation factor. A value of 0 indicates a healthy position. A value between 0 and 1e18 (WAD) indicates a soft liquidation state. A value of 1e18 (WAD) indicates a hard liquidation state.
uint256
earnTokenPrice - Current price for the earnToken (debt token).
uint256
positionTokenPrice - Current price for the positionToken (collateral token).
// Get position status
const [lFactor, earnTokenPrice, pTokenPrice] = await marketManager.liquidationStatusOf(wallet.address, eToken_Address, pToken_Address);
// Check if position is at risk
if (status.gt(ethers.utils.parseUnits('0', 18))) {
console.log('⚠️ WARNING: Position at risk of liquidation!');
}
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.
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.
EToken and PToken Contracts: Manage borrowing and collateral positions.
The position unwinding flow follows these key steps:
Initiation: User calls deleverage()
with parameters defining:
Position Token Withdrawal:
The system calls withdrawByPositionManagement()
on the position token.
This withdraws the specified collateral amount and triggers callback for specialized handling.
Collateral Conversion:
If collateral and debt tokens differ, swapping occurs.
Protocol-specific implementations define unique swapping logic.
Different adapters handle various protocols (Pendle, Velodrome, Simple swaps).
Debt Repayment:
The converted collateral is used to repay the user's debt.
The system calls repay()
on the borrow token.
Asset Return:
Any remaining collateral underlying is transferred back to user.
Any swap dust from intermediate tokens is also returned.
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.
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.
Position unwinding operations can be initiated by:
The position owner directly.
A delegated address with approved permissions.
The liquidation system (for under-collateralized positions).
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.
A typical deleveraging flow:
User has a leveraged ETH position against USDC debt.
User calls deleverage to unwind part of this position.
System withdraws ETH collateral from PToken vault.
ETH is swapped to USDC according to swap parameters.
USDC debt is repaid to the EToken contract.
Any remaining ETH and swap dust is returned to the user.
Token approvals are cleaned up.
This mechanism allows for precise management of leveraged positions while minimizing execution risk.
Description: Deleverages an existing Curvance position to decrease both collateral and debt. Includes slippage protection through the checkSlippage
modifier.
Contract: PositionManagement
Function signature:
function deleverage(
DeleverageStruct calldata deleverageData,
uint256 slippage
) external checkSlippage(msg.sender, slippage) nonReentrant
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).
Events:
// In EToken.sol
event Repay(address payer, address account, uint256 amount);
event Transfer(address indexed from, address indexed to, uint256 value);
// In MarketManager.sol
event CollateralRemoved(address account, address pToken, uint256 amount);
// Only emitted if the positionis being completely closed
event TokenPositionClosed(address mToken, address account);
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:
function deleverageFor(
DeleverageStruct calldata deleverageData,
address account,
uint256 slippage
) external checkSlippage(account, slippage) nonReentrant
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).
Events:
// In EToken.sol
event Repay(address payer, address account, uint256 amount);
event Transfer(address indexed from, address indexed to, uint256 value);
// In MarketManager.sol
event CollateralRemoved(address account, address pToken, uint256 amount);
// Only emitted if the positionis being completely closed
event TokenPositionClosed(address mToken, address account);
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.
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.
Description: Determines whether a delegate address has permission to act on behalf of a specified user.
Contract: CentralRegistry
Function signature:
Return data:
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:
Events:
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:
Return data:
Description: Checks whether delegation is temporarily or permanently disabled for a specified user.
Contract: CentralRegistry
Function signature:
Return data:
Description: Returns a user's approval index, which is used to authorize delegates across the entire protocol.
Contract: CentralRegistry
Function signature:
Return data:
Description: Increments a caller's approval index, immediately revoking all delegate permissions across all Curvance contracts.
Contract: CentralRegistry
Function signature:
Events:
Description: Determines whether a user has delegation disabled, either intentionally or due to a cooldown period.
Function signature:
Return data:
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:
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
);
The Dynamic Liquidation Engine (DLE) is a sophisticated liquidation management system implemented in the Curvance Protocol. Facilitating market collateral liquidations through a buffer-based approach with Atlas integration for OEV (Optimal Extractable Value) capture.
The contract integrates with Atlas for OEV (Optimal Extractable Value) capture:
OEV Auction Mechanism: When price updates make positions liquidatable, an off-chain auction (running ~300ms) allows liquidators to bid for execution rights.
Atomic Execution: Winning bids are executed immediately in the same block as an oracle update.
Bid Distribution: Successful liquidation bids are distributed to Curvance Protocol through the account abstraction operation.
Collateral Targeting: Each auction targets a specific collateral type, enforced through transient storage during transaction execution.
The contract implements a liquidation buffer that gives auction transactions priority access to liquidations:
Buffer Mechanism: A 10 basis point (0.1%) buffer gives auction transactions priority for liquidations.
Implementation: When MEV-boosted liquidations are detected via transient storage, liquidation health checks apply the buffer as a discount to collateral value.
Benefits:
Captures liquidations from non-oracle based changes as interest accrual or LST (Liquid Staking Token) yield distribution.
Compensates for execution latency.
Dynamic liquidation parameters are managed through transient storage:
Liquidation Penalty: Configurable within min/max bounds set by governance.
Close Factor: Determines what percentage of debt can be liquidated.
Transient Nature: Parameters exist only for the duration of a transaction.
Validation: All parameters undergo boundary validation before being applied.
The contract supports token-specific liquidations:
Token-Specific Liquidations:
Targets individual collateral positions within an account.
Uses the unlockAtlasCollateral
mechanism to specify which token can be liquidated.
Ideal for soft liquidations where only specific assets need adjustment.
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).
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.
When determining an asset's price, Curvance employs the "most safe" selection algorithm:
Each oracle reports its price for the asset.
The system applies sanity checks to both reported prices:
Deviation from previous price must not exceed configured limits.
Price must be above minimum threshold (non-zero).
Oracle must have reported within the maximum allowable reporting window.
For borrowable assets, the system selects the higher of the two valid prices.
For collateral assets, the system selects the lower of the two valid prices.
This approach ensures that in liquidation scenarios, the protocol always errs on the side of protecting itself from bad debt, while giving borrowers the benefit of the most favorable valid price.
Liquidation attempts undergo multiple validation checks.
For example, we check if collateral is unlocked for Atlas transactions:
// Will revert if during orderflow auction and liquidator has chosen incorrect collateral.
// Intended to be passed into new _liquidationStatusOf() function.
uint256 auctionBuffer = _checkCollateralUnlocked(eToken);
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.
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.
Description: Determines whether an account can be liquidated by calculating its liquidation factor (lFactor), based on the ratio of their collateral to outstanding debt. The function returns the lFactor
and current prices for the specified tokens. In Atlas transactions, a 10 basis point buffer is applied to give priority access to liquidations.
Contract: MarketManager
Function signature:
function liquidationStatusOf(
address account,
address earnToken,
address positionToken
) public view returns (uint256 lfactor, uint256 earnTokenPrice, uint256 positionTokenPrice)
address
account
The account to check liquidation status for.
address
earnToken
The eToken (debt token) to be repaid during potential liquidation.
address
positionToken
The pToken (collateral token) to be seized during potential liquidation.
Return data:
uint256
lFactor - Account's current liquidation factor. A value of 0 indicates a healthy position. A value between 0 and 1e18 (WAD) indicates a soft liquidation state. A value of 1e18 (WAD) indicates a hard liquidation state.
uint256
earnTokenPrice - Current price for the earnToken (debt token).
uint256
positionTokenPrice - Current price for the positionToken (collateral token).
Description: Checks if a liquidation should be allowed to occur for a specific account, and calculates how many position tokens should be seized when liquidating. For Atlas transactions, it verifies that the specified collateral has been unlocked using transient storage.
Contract: MarketManager
function canLiquidate(
address eToken,
address pToken,
address account,
uint256 amount,
bool liquidateExact
) external view returns (uint256, uint256)
address
eToken
Debt token to repay which is borrowed by the account.
address
pToken
Position token collateralized by the account that will be seized.
address
account
The address of the account to be liquidated.
uint256
amount
The amount of eToken underlying being repaid.
bool
liquidateExact
Whether the liquidator desires a specific liquidation amount.
Return data:
uint256
The amount of eToken underlying to be repaid on liquidation.
uint256
The number of pToken tokens to be seized in the liquidation.
Description: Similar to canLiquidate, but this function also executes collateral removal. It applies dynamic penalty and close factor values if the transaction is an Atlas transaction, using parameters stored in transient storage.
Contract: MarketManager
function canLiquidateWithExecution(
address eToken,
address pToken,
address liquidator,
address account,
uint256 amount,
bool liquidateExact
) external returns (uint256, uint256)
address
eToken
Debt token to repay which is borrowed by the account.
address
pToken
Position token which was used as collateral and will be seized.
address
liquidator
The address that will perform the liquidation.
address
account
The address of the account to be liquidated.
uint256
amount
The amount of eToken underlying being repaid.
bool
liquidateExact
Whether the liquidator desires a specific liquidation amount.
Return data:
uint256
The amount of eToken underlying to be repaid on liquidation.
uint256
The number of pToken tokens to be seized in the liquidation.
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.
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.
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.
eTokens: Debt tokens that users borrow from.
pTokens: Position tokens that users deposit as collateral.
Users first deposit assets into a pToken contract, which represents their initial collateral position.
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)
The system checks how much the user can borrow based on:
Current collateral value.
Collateralization ratio of the pToken.
Existing debt.
Available liquidity in the eToken market.
maxBorrowAmount = _maxRemainingLeverageOf(account, borrowToken)
The system borrows tokens from the specified eToken through a callback pattern:
borrowToken.borrowForPositionManagement(account, borrowAmount, leverageData)
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.
For standard ERC20 tokens, the process is straightforward:
Borrow the underlying token from an eToken.
Swap the borrowed asset for the pToken's underlying.
Deposit the resulting tokens as additional collateral.
For Pendle LP tokens, the process involves:
Borrow the underlying token from an eToken.
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.
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.
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.
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.
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.
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.
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.
Contract: PositionManagement
Description: Retrieves the current protocol leverage fee from the central registry.
Function signature:
function getProtocolLeverageFee() public view returns (uint256)
Return data:
uint256
The protocol leverage fee in WAD format (1e18).
Description: Calculates the maximum amount of debt a user can borrow for leverage after a hypothetical new position token deposit. A minor dampening effect is applied to ensure safety margins. The function also checks if there's sufficient liquidity in the borrowing market and returns whether the calculation was offset due to liquidity constraints.
Contract: PositionManagement
Function signature:
function hypotheticalMaxRemainingLeverageOf(
address account,
address borrowToken,
address positionToken,
uint256 collateralAmount
) public view returns (uint256 maxDebtBorrowable, bool isOffset)
address
account
The account to query maximum borrow amount for.
address
borrowToken
The eToken that the account will borrow from to achieve leverage.
address
positionToken
The pToken that the account will deposit to leverage against.
uint256
collateralAmount
The amount of underlying pToken that will be deposited.
Return data:
uint256
The maximum remaining borrow amount allowed from borrowToken, measured in underlying token amount.
bool
Whether the maximum borrowable debt amount returned has been offset due to available liquidity constraints.
Description: Calculates the maximum amount of debt a user can borrow for leverage based on their current position. A minor dampening effect is applied to ensure safety margins.
Contract: PositionManagement
Function signature:
function maxRemainingLeverageOf(
address account,
address borrowToken
) public view returns (uint256)
address
account
The account to query maximum borrow amount for.
address
borrowToken
The eToken that the account will borrow from to achieve leverage.
Return data:
uint256
The maximum remaining borrow amount allowed from borrowToken, measured in underlying token amount.
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,
LeverageStruct calldata leverageData,
uint256 slippage
) external checkSlippage(msg.sender, slippage) nonReentrant
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 PToken
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 EToken
event Transfer(address indexed from, address indexed to, uint256 value);
event Borrow(address account, uint256 amount);
event InterestAccrued( // Only emitted when interest is accrued on the eToken
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
// In MarketManager
event CollateralPosted(address account, address pToken, uint256 amount);
// Only emitted if this is the user's first position in the specific token
event PositionAdjusted(address mToken, address account, bool open);
event CollateralAdjusted(
address account,
address pToken,
uint256 amount,
bool increase
);
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
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 EToken.sol
event Transfer(address indexed from, address indexed to, uint256 value);
event Borrow(address account, uint256 amount);
// In MarketManager.sol
event CollateralPosted(address account, address pToken, uint256 amount);
// Only emitted if this is the user's first position in the specific token
event TokenPositionCreated(address mToken, address account);
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
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 EToken.sol
event Transfer(address indexed from, address indexed to, uint256 value);
event Borrow(address account, uint256 amount);
// In MarketManager.sol
event CollateralPosted(address account, address pToken, uint256 amount);
// Only emitted if this is the user's first position in the specific token
event TokenPositionCreated(address mToken, address account);
The Curvance orderflow auction system integrates on-chain and off-chain components to enable efficient liquidations with dynamic liquidation sizes and penalty rates.
Dynamic Liquidation Penalties: Adjustable between min and max values.
Transient Storage: Ensures penalties reset after auction transactions.
Atlas Integration: Permissionless MEV capture framework.
Oracle Integration: Curvance's pricing uses a dual-oracle system to prioritize risk mitigation, enhancing reliability and security.
Liquidation Buffer: 10 basis point margin ensuring Atlas transactions get priority for liquidations.
Efficient Batch Liquidations: Support for processing multiple liquidations in a single transaction.
The architecture consists of two primary layers:
Off-Chain Layer:
Price feed monitoring services detect significant changes.
Atlas auction system coordinates liquidator bidding.
Solver bid collection and ranking mechanism.
On-Chain Layer:
Market Manager handles liquidation execution.
Dynamic Penalty System manages incentive rates.
Atlas 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.
Oracle Update & Operation Collection: Price feed updates create liquidation opportunities. Liquidators generate signed UserOps containing bid amounts and execution instructions.
Auction & Bundling: Fastlane (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
.
Transaction Execution: The bundled transaction executes on the Atlas 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.
DApp Control: The protocol maintains control through specificSequencingActive
toggle and whitelist management of approved bundlers.
This mechanism maximizes MEV capture while ensuring liquidations always complete in a timely manner.
The journey from price change to liquidation execution follows these steps:
A price feed detects a significant change affecting position health.
Fastlane initiates an auction for liquidation rights.
Liquidators submit bids with penalty preferences and value offers.
Bids are ranked according to the system's ordering rules.
An Atlas transaction is constructed with winning bids.
Upon successful execution, auction value is distributed to stakeholders.
This auction typically completes within milliseconds off-chain before submitting the transaction.
The Atlas buffer system provides priority access to liquidations:
Buffer Mechanism: A 10 basis point (0.1%) buffer gives Atlas transactions priority for liquidations.
Implementation: When Atlas transactions are detected via transient storage, liquidation health checks apply the buffer.
Benefits:
Captures liquidations from interest accrual.
Handles LST (Liquid Staking Token) yield accumulation.
Compensates for network latency.
/// @notice Buffer to ensure Orderflow auction can do
/// interest-triggered liquidations.
/// @dev 0.999e18 = 99.9%. multiplied then divided
/// by WAD = 10 bps buffer.
uint256 public constant AUCTION_BUFFER = 0.999e18;
/// @notice Will revert and block liquidations of collateral that are not
/// currently allowed by Atlas, only if this is an Atlas tx.
function _checkCollateralUnlocked(
address eTokenToLiquidate
) internal view returns (uint256) {
uint256 result;
assembly {
result := tload(_TRANSIENT_COLLATERAL_UNLOCKED_KEY)
}
// CASE: This is not an Atlas tx, so allow all collaterals,
// and return no buffer.
if (result == 0) {
return 0;
}
address unlockedCollateral = address(uint160(result));
// This is an Atlas tx, and Atlas liquidator attempted wrong
// collateral so revert.
if (unlockedCollateral != eTokenToLiquidate) {
_revert(_UNAUTHORIZED_COLLATERAL_SELECTOR);
}
// if we reach this point this is an Atlas tx and collateral is valid,
// so return the atlas buffer.
return AUCTION_BUFFER;
}
The dynamic penalty mechanism operates as follows:
Liquidators submit bids indicating their preferred penalty and close factor rate.
During Atlas transaction execution, the DAppControl calls setAtlasParameters()
.
The Market Manager stores this value in transient storage.
Liquidation executes using the dynamic penalty value.
Upon transaction completion, the penalty automatically resets to default.
This design ensures that penalties remain active only during the specific liquidation transaction, preventing any persistent state changes.
The system includes an effective fallback process for scenarios when Atlas is temporarily unavailable:
When Atlas is down, the system automatically falls back into using the base liquidation incentive and close factor values.
Regular liquidations proceed without the 10 bps advantage.
The fallback occurs seamlessly without delays.
Price feeds continue to function through a secondary oracle service.
The dynamic penalty system uses transient storage (tstore
/tload
) to ensure penalties only exist within the context of Atlas transactions.
The liquidation penalty value follows a simple state machine:
Default Penalty: Base incentive rate configured by governance.
Dynamic Penalty: Temporary value set during Atlas transaction.
Reset to Default: Automatic transition upon transaction completion.
The system uses transient storage to ensure dynamic penalties cannot persist beyond their intended scope.
// MarketManagerIsolated.sol
/// Sets new dynamic close factor and liquidation penalty values in transient storage.
function setAtlasParameters(uint256 newPenalty, uint256 newCloseFactor) external {
_checkAtlasPermissions();
// Validate new Liquidation Penalty value.
MarketToken storage pToken = tokenData[positionToken];
// Validate new penalty is within configured allowed penalty.
if (newPenalty < pToken.liqMinIncentive || newPenalty > pToken.liqMaxIncentive) {
revert MarketManager__InvalidParameter();
}
// Validate new Close Factor value.
if (newCloseFactor < pToken.minEffectiveCloseFactor || newCloseFactor > pToken.maxEffectiveCloseFactor) {
revert MarketManager__InvalidParameter();
}
// Set new Risk Parameters in transient storage.
// tstore(key, value): store `newPenalty` under TRANSIENT_PENALTY_KEY.
assembly {
tstore(_TRANSIENT_PENALTY_KEY, newPenalty)
}
// tstore(key, value): store `newCloseFactor` under TRANSIENT_CLOSE_FACTOR_KEY.
assembly {
tstore(_TRANSIENT_CLOSE_FACTOR_KEY, newCloseFactor)
}
}
/// Resets the Atlas risk parameters in transient storage to zero.
function resetAtlasParameters() external {
_checkAtlasPermissions();
assembly {
// Clear the transient storage slot by writing zero.
tstore(_TRANSIENT_PENALTY_KEY, 0)
}
// Clear the transient storage slot by writing zero.
assembly {
tstore(_TRANSIENT_CLOSE_FACTOR_KEY, 0)
}
}
/// @notice Returns the current Atlas parameters.
function getLatestAtlasParameters() public view returns (
uint256 penalty,
uint256 closeFactor
) {
assembly {
penalty := tload(_TRANSIENT_PENALTY_KEY)
closeFactor := tload(_TRANSIENT_CLOSE_FACTOR_KEY)
}
// Fallback scenarios where a parameter(s) MUST based handled
// separately based on lFactor
}
The system enforces that only specific collateral can be liquidated during Atlas transactions:
function unlockAtlasCollateral(address collateralToUnlock) external {
uint256 collateralToUnlockUint = uint256(uint160(collateralToUnlock));
_checkAtlasPermissions();
assembly {
tstore(_TRANSIENT_COLLATERAL_UNLOCKED_KEY, collateralToUnlockUint)
}
}
function lockAtlasCollateral() external {
_checkAtlasPermissions();
assembly {
tstore(_TRANSIENT_COLLATERAL_UNLOCKED_KEY, 0)
}
}
The system supports efficient multi-liquidation processing:
Bulk Processing: Can handle multiple liquidations in a single transaction.
Cached Pricing: Retrieves asset prices once per transaction rather than per liquidation.
Consolidated Transfers: Aggregates debt repayments into a single transfer.
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.
The Atlas auction system is the core mechanism for capturing liquidation MEV in Curvance. When a liquidation opportunity arises, the system conducts a brief (typically 300ms) auction before submitting an on-chain transaction.
Each liquidation bid consists of these key elements:
Solver Address: The liquidator's smart contract that will execute the liquidation.
Execution Data: Calldata specifying which account to liquidate and method parameters.
Penalty Bid: The liquidation penalty rate the liquidator is willing to accept.
Close Factor Bid: The percentage of debt the liquidator wants to liquidate.
OEV Bid Amount: Payment offered to the protocol and other stakeholders.
Signature: Cryptographic verification of the bid's authenticity.
The auction system automatically classifies bids into two distinct categories:
Minimum Penalty Bids:
Uses the protocol-defined minimum penalty (e.g., 5%).
Must include a positive OEV payment amount.
Typically used during normal market conditions.
Higher Penalty Bids:
Specifies a penalty above the minimum (e.g., 6-20%).
No OEV payment required.
Used during high volatility or significant slippage.
The auction employs a specialized ranking system with these core rules:
Primary Ranking by Penalty Tier:
Minimum penalty bids compete exclusively against other minimum penalty bids.
Higher penalty bids compete based on the penalty rate offered.
Secondary Ranking Rules:
For minimum penalty bids: Higher OEV payment receives priority.
For higher penalty bids: Lower penalty rate receives priority.
Tertiary Sorting: When OEV payments or penalty rates are identical, bids are ordered by timestamp.
Example 1: Minimum Penalty Competition
Consider these bids at the minimum 5% penalty:
LiquidatorA
5%
10 ETH
10:00:01
LiquidatorB
5%
8 ETH
10:00:00
LiquidatorC
5%
12 ETH
10:00:02
Ranking order: LiquidatorC → LiquidatorA → LiquidatorB (sorted by highest OEV payment).
Example 2: Higher Penalty Competition
Consider these bids with penalties above minimum:
LiquidatorA
7%
0
10:00:01
LiquidatorB
8%
0
10:00:00
LiquidatorC
6%
0
10:00:02
Ranking order: LiquidatorC → LiquidatorA → LiquidatorB (sorted by lowest penalty).
Example 3: Mixed Competition
When both minimum and higher penalty bids exist:
LiquidatorA
5%
10 ETH
10:00:01
LiquidatorB
7%
0
10:00:00
LiquidatorC
5%
12 ETH
10:00:02
Ranking will attempt LiquidatorC first (highest OEV at min penalty), then LiquidatorA (second highest OEV at min penalty), then LiquidatorB (lowest penalty among higher penalty bids).
The Atlas transaction processes these bids sequentially:
The highest-ranked bid's solver contract is called first.
If execution succeeds, the transaction completes and value is distributed.
If execution fails, the next-ranked bid is attempted.
This continues until either a liquidation succeeds.
When a bid succeeds, the value is distributed according to the protocol's configured rules:
As specified in the DAppControl contract, the allocateValue hook distributes the OEV payment between:
The bundler (who submits the transaction).
Oracle (price feed provider).
Fastlane (Operations Relay infrastructure).
The Curvance treasury.
The exact distribution percentages are configured by Curvance governance.
OEV bids are only present when a solver wins with a penalty bid equal to the minimum penalty.
No OEV payment is included or distributed.
All excess value from the higher penalty accrues to the liquidator.
No value distribution to protocol or infrastructure stakeholders occurs.
This dual economic model aligns incentives effectively: during normal market conditions, the protocol captures value through OEV payments, while during stressed market conditions, liquidators retain more value to offset increased risk and ensure liquidations complete successfully.
Push vs. Pull Model:
Atlas OEV requires a push-based oracle model where price updates trigger on-chain events.
Each price update can initiate liquidation opportunities.
Transaction Sequencing:
When an oracle update transaction lands on-chain, it may trigger liquidations.
Atlas integrates with the oracle update process to capture OEV (Optimal Extractable Value).
Oracle Compatibility:
Designed to work with any push-based oracle.
RedStone push feeds are the primary integration target.
Support for LST (Liquid Staking Token) redemption rate oracles.
The system employs two distinct storage layers:
Permanent Storage:
Penalty ranges (min, max, default values).
Account health statuses.
System configuration parameters.
Transient Storage:
Dynamic penalty values during Atlas transactions.
Close factor values.
Collateral unlock status.
Values automatically reset after transaction completion.
This separation ensures critical data persists while dynamic values remain ephemeral.
Resources:
Atlas Docs: https://atlas-docs.pages.dev/atlas/introduction
The Dynamic Interest Rate Model (DIRM) 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.
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.
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.
Storage Format: Packed with timestamp data in a single storage slot to minimize gas costs.
Market Utilization → Calculated from borrows / (underlyingHeld + borrows - reserves).
Utilization → Drives interest rate calculations through base and vertex formulas.
Interest Rates → Applied to borrowers and distributed to lenders (minus protocol fees).
Vertex Multiplier → Adjusted based on sustained market utilization patterns.
Decay Mechanism → Continuously reduces elevated multiplier values over time.
The Vertex Multiplier operates as a state machine with the following 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
Transitions only occur when current block.timestamp ≥ updateTimestamp
.
Rate update only possible when properly linked to an eToken.
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.
Similarly, if utilization is low and the multiplier has been reduced, it decays back upward over time to prevent excessively low rates.
Creates natural incentives for borrowers while protecting against liquidity crunches.
The model is governed by several parameters that define its behavior:
Base Parameters:
baseInterestRate: The slope of the linear portion.
vertexInterestRate: The slope of the exponential portion.
vertexStartingPoint: The utilization point where vertex takes effect.
Adjustment Controls:
adjustmentRate: Time between multiplier updates (seconds).
adjustmentVelocity: Maximum rate of multiplier change per update.
vertexMultiplierMax: Maximum allowed value for multiplier.
Threshold Parameters:
increaseThreshold: Point above vertex where multiplier increases.
increaseThresholdMax: Point where multiplier increase reaches maximum.
decreaseThreshold: Point below vertex where multiplier decreases.
decreaseThresholdMax: Point where multiplier decrease reaches maximum.
decayRate: Rate at which elevated multipliers naturally decrease.
Responsive to Market Conditions:
High utilization leads to increased rates, attracting lenders.
Sustained high rates encourage borrowers to repay.
Self-Balancing:
Creates a feedback loop that stabilizes market liquidity.
Prevents liquidity crunches through predictive rate adjustments.
Growth Incentives:
Decay mechanism helps maintain competitive rates during normal operations.
Creates naturally decreasing interest rates in stable markets.
Gas Optimization:
Uses bit-packed storage for multiplier and timestamp.
Efficient math calculations for model computation.
If a market experiences sustained high utilization:
Interest rates will gradually increase as the Vertex Multiplier rises.
This attracts new lenders while encouraging borrowers to repay.
As utilization decreases, rates begin to fall (but not immediately due to the multiplier).
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.
Utilization Rate Calculation
function utilizationRate(...) {
if (borrows == 0) {
return 0;
uint256 utilRate = (borrows * WAD) /
(underlyingHeld + borrows - reserves);
// If reserves end up growing too much and cause util > 100%,
// cap it to 100%.
return utilRate > WAD ? WAD : utilRate;
}
Base Interest Rate Calculation
function _getBaseInterestRate(uint256 util) internal view returns (uint256) {
return (util * ratesConfig.baseInterestRate) / WAD;
}
Vertex Interest Rate Calculation
function _getVertexInterestRate(uint256 util) internal view returns (uint256) {
return (util * ratesConfig.vertexInterestRate * vertexMultiplier()) / WAD;
}
Final Borrow Interest Rate Calculation
function getBorrowRate(...) {
if (util <= vertexPoint) {
return _getBaseInterestRate(util);
}
return (_getVertexInterestRate(util - vertexPoint) +
_getBaseInterestRate(vertexPoint));
}
Vertex Multiplier Adjustment (Above Vertex)
Where:
function _getPositiveCFactorResult(...) {
uint256 cFactor = ((current - start) * WAD) / (end - start);
cFactor = WAD_SQUARED + (cFactor * adjustmentVelocity);
return ((multiplier * cFactor) / WAD_SQUARED) - decay;
}
Vertex Multiplier Adjustment (Below Vertex)
function _getNegativeCFactorResult(...) {
newMultiplier = ((currentMultiplier * WAD) / (WAD + config.adjustmentVelocity)) - decay;
return newMultiplier < WAD ? WAD : newMultiplier;
}
Contract: DynamicInterestRateModel
Description: Calculates the current borrow rate per year, with updated vertex multiplier applied.
Function signature:
function getPredictedBorrowRatePerYear(
uint256 underlyingHeld,
uint256 borrows,
uint256 reserves
) external view returns (uint256)
uint256
underlyingHeld
The amount of underlying assets held in the market.
uint256
borrows
The amount of borrows in the market.
uint256
reserves
The amount of reserves in the market.
Return data:
uint256
The borrow rate percentage per year, in WAD
Contract: DynamicInterestRateModel
Description: Calculates the current supply rate per year. This function converts the per-compound supply rate to an annual rate.
Function signature:
function getSupplyRatePerYear(
uint256 underlyingHeld,
uint256 borrows,
uint256 reserves,
uint256 interestFee
) external view returns (uint256)
uint256
underlyingHeld
The amount of underlying assets held in the market.
uint256
borrows
The amount of borrows in the market.
uint256
reserves
The amount of reserves in the market.
uint256
interestFee
The current interest rate reserve factor for the market.
Return data:
uint256
The supply rate percentage per year, in WAD.
Contract: DynamicInterestRateModel
Description: Returns the unpacked values from _currentRates storage variable, providing the current vertex multiplier and next update timestamp.
Function signature:
function currentRatesData() external view returns (uint256, uint256)
Return data:
uint256
The current Vertex Multiplier, in WAD.
uint256
The timestamp for the next vertex multiplier update, in unix time.
Contract: DynamicInterestRateModel
Description: Calculates the utilization rate of the market using the formula: borrows / (underlyingHeld + borrows - reserves).
Function signature:
function utilizationRate(
uint256 underlyingHeld,
uint256 borrows,
uint256 reserves
) public pure returns (uint256)
uint256
underlyingHeld
The amount of underlying assets held in the market.
uint256
borrows
The amount of borrows in the market.
uint256
reserves
The amount of reserves in the market.
Return data:
uint256
The utilization rate between [0, WAD].
Contract: DynamicInterestRateModel
Description: Calculates the current borrow rate per compound, with updated vertex multiplier applied. This provides a prediction of what the borrow rate will be after the next vertex multiplier update.
Function signature:
function getPredictedBorrowRate(
uint256 underlyingHeld,
uint256 borrows,
uint256 reserves
) public view returns (uint256)
uint256
underlyingHeld
The amount of underlying assets held in the market.
uint256
borrows
The amount of borrows in the market.
uint256
reserves
The amount of reserves in the market.
Return data:
uint256
The borrow rate percentage per compound, in WAD.
Contract: DynamicInterestRateModel
Description: Calculates the current borrow rate, per compound, based on the current market conditions.
Function signature:
function getBorrowRate(
uint256 underlyingHeld,
uint256 borrows,
uint256 reserves
) public view returns (uint256)
uint256
underlyingHeld
The amount of underlying assets held in the market.
uint256
borrows
The amount of borrows in the market.
uint256
reserves
The amount of reserves in the market.
Return data:
uint256
The borrow rate percentage, per compound, in WAD.
Contract: DynamicInterestRateModel
Description: Calculates the current supply rate, per compound, based on the borrow rate and interest fee.
Function signature:
function getSupplyRate(
uint256 underlyingHeld,
uint256 borrows,
uint256 reserves,
uint256 interestFee
) public view returns (uint256)
uint256
underlyingHeld
The amount of underlying assets held in the market.
uint256
borrows
The amount of borrows in the market.
uint256
reserves
The amount of reserves in the market.
uint256
interestFee
The current interest rate reserve factor for the market.
Return data:
uint256
The supply rate percentage, per compound, in WAD
Contract: DynamicInterestRateModel
Description: Returns the multiplier applied to the vertex interest rate, which is used to dynamically adjust interest rates based on market conditions.
Function signature:
function vertexMultiplier() public view returns (uint256)
Return data:
uint256
The multiplier applied to the vertex interest rate, in WAD.
Contract: DynamicInterestRateModel
Description: Returns the next timestamp when the vertex multiplier will be updated.
Function signature:
function updateTimestamp() public view returns (uint256)
Return data:
uint256
The next timestamp when vertexMultiplier will be updated, in unix time.
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.
Liquidity Manager: Calculates account liquidity across positions.
Liquidation Manager: Manages liquidation queues and OEV auction integration.
Position Management: Handles position creation, modification, and leverage.
Dynamic Interest Rate Model: Manages borrow and supply interest rates for Curvance debt tokens.
Market Tokens: Tokens in Curvance that represent collateral, and debt.
.
Two primary token types exist within Curvance markets:
Position Tokens (pTokens): Represent collateral positions.
Debt Tokens (eTokens): Represent borrowed assets.
Together, these are collectively called Market Tokens (mTokens).
Users deposit assets into pToken contracts.
pTokens can be posted as collateral (subject to collateral caps).
Posted collateral enables borrowing capacity.
Market Manager tracks all collateral positions.
When OEV is enabled, the system prioritizes auction winners for liquidations:
Block N:Oracle update lands on-chain
Auctioneer/bundler (Fastlane) submits winning liquidation bids.
OEV liquidations execute immediately if caller is whitelisted.
Fallback Mechanism:
If OEV auctions are offline or fail, system falls back to queued liquidations.
Liquidators must call queueLiquidation()
to enter the queue.
Liquidations proceed through priority → regular → end phases.
The Market Manager implements a three-tiered liquidation threshold system:
Soft Liquidation Threshold (collReqSoft
)
Initiates partial liquidations with base incentives.
Minimal disruption to user positions.
Hard Liquidation Threshold (collReqHard
)
Permits complete position liquidation.
Maximum liquidation incentives.
Bad Debt Threshold
Triggered when collateral value falls below total debt.
Bad debt is socialized among lenders.
Every collateral asset has a Collateral Cap measured in shares.
As collateral is posted, the collateralPosted
invariant increases.
Caps can be decreased without forcing unwinding of existing positions.
Prevents excessive concentration of risky assets.
Curvance employs a 20-minute minimum duration requirement for:
Posting pToken collateral.
Lending/borrowing eTokens.
This cooldown period:
Improves protocol security.
Prevents flash loan attacks.
Enables more sophisticated interest rate models.
Reduces market manipulation opportunities.
The state transition looks like:
The Dynamic Interest Rate Model adjusts rates based on market conditions:
Base rate increases linearly until vertex point is reached.
Includes dynamic Vertex Multiplier that adjusts based on liquidity utilization.
Higher utilization increases borrowing costs to incentivize repayments and new lenders.
Decay mechanism balances rate reversion during normal conditions.
20-minute minimum duration for posting collateral and lending.
No rehypothecation of user deposits.
Isolation of market risks through market-specific tokens.
Bad debt socialization system to protect protocol solvency.
Curvance implements two distinct types of market managers to address different risk models and use cases: Cross Market Managers and Isolated Market Managers.
The Cross MarketManager contract supports multiple collateral assets and debt positions within a single market environment. This design enables users to post various types of collateral against different debt positions, thereby creating a diverse risk portfolio under a single manager. Cross market managers are well-suited for ecosystems with complementary assets that share similar risk characteristics, enabling efficient capital utilization across the entire portfolio. The protocol can optimize risk management at a holistic level, with liquidation and collateralization requirements balanced across all positions.
The MarketManagerIsolated contract focuses on a single position token type as collateral. This design creates a thesis-driven micro-ecosystem that isolates risk to specific asset classes or strategies. Isolated markets are ideal for specialized use cases, such as interest-bearing stablecoins, volatile LP tokens, or other assets with unique risk profiles that shouldn't contaminate other markets. This separation prevents contagion between different risk profiles while still enabling the protocol to support diverse asset types across separate, isolated markets. Each isolated market can be configured with parameters specifically tailored to its underlying asset's volatility and liquidity characteristics.
Both designs contribute to Curvance's approach of minimizing systemic risk while maximizing the range of supportable assets, allowing the protocol to safely incorporate nearly any ERC20 token into its ecosystem through the appropriate market manager structure.
The Market Manager interacts with multiple other components:
Central Registry for protocol parameters and permissions.
Oracle Manager for price feeds.
Token contracts for position management.
Position Management contracts for leveraging operations.
External bundlers/auctioneers for OEV liquidations.
Through these interactions, the Market Manager maintains the integrity and stability of each market while optimizing capital efficiency and risk management.
Contract: MarketManager
Description: Checks if an mToken (pToken or eToken) is listed in the lending market.
Function signature:
function isListed(address mToken) external view returns (bool)
address
mToken
Address of the pToken or eToken.
Return data:
bool
Whether the token is an mToken (true), or not (false).
Contract: MarketManager
Description: Returns an array of all mToken addresses that are listed in the market.
Function signature:
function queryTokensListed() external view returns (address[] memory)
Return Data:
address[]
Array of all mToken addresses listed in the market.
Contract: MarketManager
Description: Returns all assets that an account has entered.
Function signature:
function assetsOf(address account) external view returns (IMToken[] memory)
address
account
The address of the account to query.
Return data:
IMToken[]
Array of mTokens that the account has entered.
Contract: MarketManager
Description: Returns detailed information about an account's position in a specific market token, including whether they have an active position, their balance, and collateral posted.
Function signature:
function tokenDataOf(address account, address mToken) external view returns (bool hasPosition, uint256 balanceOf, uint256 collateralPostedOf)
address
account
The address of the account to check.
address
mToken
The address of the market token.
Return data:
bool
Whether the account has an active position in the specified market token.
uint256
The account's balance of the specified market token.
uint256
The amount of collateral posted by the account in the specified market token.
Contract: MarketManagerCross
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)
address
account
The account to determine liquidity for.
Return data:
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.
Contract: MarketManager
Description: Calculates what an account's liquidity would be after a hypothetical action such as redeeming or borrowing. Will natively revert if a hypothetical borrow will result in a loan less than MIN_ACTIVE_LOAN_SIZE
, set in LiquidityManager
.
Function signature:
function hypotheticalLiquidityOf(
address account,
address mTokenModified,
uint256 redeemTokens, // in Shares.
uint256 borrowAmount // in Assets.
) external view returns (uint256, uint256, bool[] memory)
address
account
The account to determine liquidity for.
address
mTokenModified
The market to hypothetically redeem/borrow in.
uint256
redeemTokens
The number of tokens to hypothetically redeem in shares.
uint256
borrowAmount
The amount of underlying to hypothetically borrow in assets.
Return data:
uint256
Hypothetical account liquidity in excess of collateral requirements.
uint256
Hypothetical account liquidity deficit below collateral requirements.
bool[]
An array of whether the positions should be closed or not.
Contract: MarketManager
Description: Posts tokens as collateral in the market (subject to cooldown period). Caller must be the same as account
or the pToken contract associated. The account must have sufficient pTokens to post as collateral. The pToken configuration must have collateralization enabled.
Function signature:
function postCollateral(
address account,
address pToken,
uint256 tokens) external
address
account
The address of the account posting collateral.
address
pToken
The address of the position token to use as collateral.
uint256
tokens
The amount of tokens to post as collateral, in shares.
Contract: MarketManager
Description: Removes collateral from the market (subject to cooldown period). Only called by a user's address. Can only succeed if the account has no active loan, if the removal does not cause a liquidity deficit, or past the 20 minute hold period. Also clears inactive positions in the MarketManager's internal accounting.
Function signature:
function removeCollateral(address pToken, uint256 tokens) external
address
pToken
The address of the position token to remove collateral for.
uint256
tokens
The number of tokens to remove from collateral, in shares.
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 mTokens to check if minting is paused.
Function signature:
function canMint(
address mToken,
address account,
uint256 amount) external view
address
mToken
The market token address to verify minting for.
address
account
The account which would mint the tokens.
uint256
amount
The amount of underlying asset the account would deposit.
Contract: MarketManagerCross and 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.
Function signature:
function canRedeem(address mToken,
address account,
uint256 amount) external view returns (uint256, bool[] memory)
address
mToken
The market token to verify redemption for.
address
account
The account which would redeem the tokens.
uint256
amount
The number of tokens to redeem for the underlying asset.
Return data:
uint256
Flag indicating if positions need to be closed after redemption.
bool[]
Array indicating which positions should be closed.
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 mToken, address account) external view
address
mToken
The market token to verify the repayment for.
address
account
The account who will have their loan repaid.
Important market states are stored in variables, but can still be called using a contract interface
Contract: MarketManagerCross and MarketManagerIsolated
Description: Checks if minting of a particular mToken is paused or not.
mapping(address => uint256) public mintPaused;
Inputs:
address
Address of the mToken to check the minting status of.
Return data:
uint256
Return value of 0 or 1 indicates minting is not paused. A value of 2 or greater indicates it is paused.
Contract: MarketManagerCross and MarketManagerIsolated
Description: Checks if borrowing for a particular eToken is paused or not.
mapping(address => uint256) public borrowPaused;
Inputs:
address
Address of the eToken to check the borrowing status of.
Return data:
uint256
Return value of 0 or 1 indicates borrowing is not paused. A value of 2 or greater indicates it is paused.
Contract: MarketManagerCross and MarketManagerIsolated
Description: Returns the amount of pTokens that has been posted as collateral in shares.
mapping(address => uint256) public collateralPosted;
Inputs:
address
Address of the pToken to check the amount of collateral posted.
Return data:
uint256
amount of pTokens that has been posted as collateral in shareA.
Contract: MarketManagerCross and MarketManagerIsolated
Description: Returns the amount of pToken that can be posted of collateral, in shares.
mapping(address => uint256) public collateralCaps;
Inputs:
address
Address of the pToken to check the max amount of shares that can be posted as collateral.
Return data:
uint256
amount of pTokens that has can be posted as collateral.
Position Tokens (pTokens) are Curvance's collateral tokens that represent user deposits in various yield-generating strategies. These ERC4626-compliant tokens allow users to deposit assets into Curvance vaults and optionally use them as collateral for borrowing in the lending markets. pTokens are designed to be fully liquid, ensuring that assets can be immediately withdrawn or liquidated if needed.
pTokens follow a hierarchical inheritance structure:
BasePToken: The foundational abstract contract implementing ERC4626 vault functionality.
SimplePToken: For basic assets that don't generate external rewards (e.g., WETH, stablecoins).
CompoundingPToken: Extended functionality for assets that generate yield (supports auto-compounding).
CompoundingWithExitFeePToken: Adds an exit fee mechanism to compounding vaults.
Protocol-Specific Implementations: Custom implementations for different DeFi protocols.
pTokens interact with several core Curvance contracts:
CentralRegistry: Protocol configuration and permissions management.
MarketManager: Handles collateral position registration and risk parameters.
Various External Protocol Contracts: For staking, providing liquidity, and claiming rewards.
Simple pTokens (SimplePToken
) are designed for assets that don't generate external rewards. They provide a straightforward wrapper for assets like:
Wrapped ETH
Liquid Staking Tokens (LSTs)
Principal Tokens
Stablecoins
Yield-bearing stablecoins
Compounding pTokens (CompoundingPToken
) extend basic functionality by adding auto-compounding yield features. These vaults:
Automatically harvest rewards.
Convert rewards back into the underlying asset.
Reinvest into the yield-generating position.
Distribute yield to all vault users through an increasing share value.
Curvance offers various protocol-specific pToken implementations, but not limited to:
AuraPToken: For Aura Finance (Balancer) LP positions.
Convex2PoolPToken/Convex3PoolPToken: For Curve/Convex 2-token and 3-token LP positions.
VelodromeStablePToken/VelodromeVolatilePToken: For Velodrome stable and volatile LP positions.
AerodromeStablePToken/AerodromeVolatilePToken: For Aerodrome stable and volatile LP positions.
StakedGMXPToken: For staked GMX positions.
PendleLPPToken: For Pendle LP positions.
Each implementation handles the specific deposit, withdrawal, and reward harvesting logic for its respective protocol.
pTokens maintain several key state variables:
User calls deposit()
with underlying assets.
Contract calculates shares based on current exchange rate.
If it's a compounding vault, the _afterDeposit()
hook is called to stake tokens in the external protocol.
Underlying tokens are transferred from the user to the contract.
Vault shares (pTokens) are minted to the user.
If user selects to use as collateral, the vault notifies the MarketManager.
User calls withdraw()
with the amount of assets to withdraw.
If it's a compounding vault, the _beforeWithdraw()
hook is called to unstake tokens from the external protocol.
Contract calculates shares to burn based on current exchange rate.
Underlying tokens are transferred from the contract to the user.
Vault shares (pTokens) are burned.
If user had posted the tokens as collateral, the vault notifies the MarketManager.
User calls postCollateral()
to use pTokens as borrowing collateral.
Contract verifies the asset is eligible as collateral and under the global cap.
MarketManager is notified of the new collateral position.
User's pTokens are marked as collateral and transfer-restricted.
harvest()
function is called (permissioned or by a keeper).
External rewards are claimed from the underlying protocol.
Rewards are swapped back to the underlying asset.
New tokens are deposited back into the strategy.
Yield is gradually distributed to all vault users through a vesting mechanism.
Compounding pTokens use a unique vesting approach to distribute yield:
Harvested rewards are not immediately reflected in the vault's total assets.
Instead, rewards vest linearly over a defined period (default is 1 day).
This creates a smoother increase in share price and reduces MEV opportunities.
The vesting mechanism is implemented through the following structure:
pTokens incorporate multiple security measures:
Collateral Restrictions: Assets posted as collateral cannot be transferred.
Pause Mechanisms: Deposit, withdrawal, and harvesting can be paused independently.
Reentrancy Protection: All critical functions have reentrancy guards.
Slippage Protection: Swap and liquidity operations have minimum output requirements.
Chain Validation: Protocol-specific implementations validate they're on the correct chain.
Permissioned Operations: Sensitive functions restricted to DAO or elevated permissions.
pTokens integrate with Curvance's oracle system for accurate valuation:
Protocol-Specific Adaptors: Custom oracle adaptors for complex assets like Pendle LP tokens.
TWAP Support: Time-weighted average prices for more accurate valuations.
Price Feeds: Integration with primary price oracles for underlying assets.
Contract: pToken
Description: Deposits assets into the vault and receives shares.
Function signature:
Return data:
Events:
Contract: pToken
Description: Deposits assets and marks shares as collateral in one transaction.
Function signature:
Return Data:
Events:
Contract: pToken
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:
Return Data:
Events:
Contract: pToken
Description: User specifies shares to receive and deposits corresponding assets.
Function signature:
Return Data:
Events:
Contract: pToken
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 pTokens, they must first have enough approval.
Function signature:
Return data:
Events:
Function: Burns shares to receive assets.
Function signature:
Return data:
Events:
Description: Redeems collateralized shares to receive assets.
Function signature:
Return data:
Events:
Description: Withdraws assets, quoted in shares from the market, and burns owner shares, on behalf of owner.
Function signature:
Return data:
Events:
redeemCollateralFor()
Description: Redeems collateralized shares on behalf of an owner.
Function signature:
Return data:
Events:
For compounding vaults like AuraPToken, Convex2PoolPToken, Convex3PoolPToken, and StakedGMXPToken:
Compounding vaults use a vesting mechanism to gradually distribute yield:
When yield is harvested:
Rewards are claimed from the external protocol.
A protocol fee is taken.
Remaining rewards are swapped to the underlying asset.
The yield is distributed over the vesting period (default 1 day).
Returns current information about the vault's yield distribution:
rewardRate
: Rate at which yield is being distributed.
vestingPeriodEnd
: When the current vesting period ends.
lastVestClaim
: Last time vesting rewards were claimed.
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.
// Modified ERC4626 state
// Total PToken underlying token assets, minus pending vesting.
uint256 internal _totalAssets;
// balanceOf seed for deterministic balance slot calculation
uint256 internal constant _BALANCE_SLOT_SEED = 0x87a211a2;
// Compounding-specific state
struct VaultData {
uint176 rewardRate;
uint40 vestingPeriodEnd;
uint40 lastVestClaim;
}
// Protocol-specific strategy data
// (Example from AuraPToken)
struct StrategyData {
IBalancerVault balancerVault;
bytes32 balancerPoolId;
uint256 pid;
IBaseRewardPool rewarder;
IBooster booster;
address[] rewardTokens;
address[] underlyingTokens;
}
struct VaultData {
uint176 rewardRate; // Rate at which rewards vest per second
uint40 vestingPeriodEnd; // When current vesting period ends
uint40 lastVestClaim; // Last time vested rewards were claimed
}
function deposit(uint256 assets, address receiver) external returns (uint256 shares)
uint256
assets
The amount of underlying assets to deposit.
address
receiver
The account that should receive the pToken shares.
uint256
shares
The amount of pToken shares received by receiver.
// From pToken
/// `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;
function depositAsCollateral(
uint256 assets,
address receiver) external returns (uint256 shares)
uint256
assets
The amount of underlying assets to deposit.
uint256
receiver
The account that should receive the pToken shares.
uint256
shares
The amount of pToken shares received by receiver.
// From PToken
/// `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 CollateralAdjusted(
address account,
address pToken,
uint256 amount,
bool increase
);
// Only emitted if this is the user's first position in this pToken.
event PositionAdjusted(address mToken, address account, bool open);
function depositAsCollateralFor(
uint256 assets,
address receiver
) external nonReentrant returns (uint256 shares);
uint256
assets
The amount of underlying assets to deposit.
uint256
receiver
The account that should receive the pToken shares.
uint256
shares
The amount of pToken shares received by receiver.
// From PToken
/// `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 CollateralAdjusted(
address account,
address pToken,
uint256 amount,
bool increase
);
// Only emitted if this is the user's first position in this pToken.
event PositionAdjusted(address mToken, address account, bool open);
function mint(
uint256 shares,
address receiver) external returns (uint256 assets)
uint256
shares
The amount of shares to mint.
address
receiver
The account that should receive the pToken shares.
uint256
shares
The amount of pToken shares received by receiver.
// From PToken
/// `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;
function withdraw(
uint256 assets,
address receiver,
address owner
) public override nonReentrant returns (uint256 shares) {
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.
uint256
shares
The amount of shares that were burned.
// From PToken
/// @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
// Only emitted if the withdrawal requires collateral removal.
event CollateralAdjusted(
address account,
address pToken,
uint256 amount,
bool increase
);
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets)
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.
uint256
assets
The amount of underlying assets sent to the receiver.
// From PToken
/// @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
// Only emitted if the withdrawal requires collateral removal.
event CollateralAdjusted(
address account,
address pToken,
uint256 amount,
bool increase
);
function redeemCollateral(
uint256 shares,
address receiver) external returns (uint256 assets)
uint256
shares
The amount of collateralized shares to redeem.
address
receiver
The account that should receive the assets.
uint256
assets
The amount of underlying assets sent to the receiver.
// From PToken
/// @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 CollateralAdjusted(
address account,
address pToken,
uint256 amount,
bool increase
);
function redeemFor(
uint256 shares,
address receiver,
address owner) public returns (uint256 assets)
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.
uint256
assets
The amount of underlying assets sent to the receiver.
// From PToken
/// @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
// Only emitted if the withdrawal requires collateral removal
event CollateralAdjusted(
address account,
address pToken,
uint256 amount,
bool increase
);
function redeemCollateralFor(
uint256 shares,
address receiver,
address owner) external returns (uint256 assets)
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.
uint256
assets
The amount of underlying assets sent to the receiver.
// From PToken
/// @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 CollateralAdjusted(
address account,
address pToken,
uint256 amount,
bool increase
);
function harvest(bytes calldata data) external returns (uint256 yield)
data
Encoded swap parameters to convert rewards to underlying assets.
function getVaultYieldStatus() external view returns (VaultData memory)
CurvanceAuxiliaryData is an auxiliary contract designed for querying comprehensive data from the Curvance ecosystem. It serves as an all-in-one interface for data aggregators and frontends to efficiently retrieve protocol information with minimal RPC calls. By consolidating multiple variable fetches into single view functions, it significantly reduces the number of EVM instances needed to perform desired queries. The contract contains no active storage values (beyond configuration) and consists entirely of view functions, making it seamlessly upgradeable to support new data formats or query requirements.
Description: Returns the current Total Value Locked (TVL) across all Curvance markets.
Function signature:
function getTotalTVL() public view returns (uint256 result)
Return data:
uint256
The current TVL inside Curvance, in WAD (18 decimals)
Description: Returns the current collateral TVL across all Curvance markets.
Function signature:
function getTotalCollateralTVL() public view returns (uint256 result)
Return data:
uint256
The current collateral TVL inside Curvance, in WAD (18 decimals)
Description: Returns the current lending TVL across all Curvance markets.
Function signature:
function getTotalLendingTVL() public view returns (uint256 result)
Return data:
uint256
The current lending TVL inside Curvance, in WAD (18 decimals)
Description: Returns the current outstanding borrows across all Curvance markets.
Function signature:
function getTotalBorrows() public view returns (uint256 result)
Return data:
uint256
The current outstanding borrows inside Curvance, in WAD (18 decimals)
getMarketManagers()
Description: Returns all registered market managers from the Central Registry.
Function signature:
function getMarketManagers() public view returns (address[] memory)
Return data:
address[]
Array of market manager addresses registered in Curvance
Description: Returns if an account has an active position in a token, along with balances and collateral posted.
Function signature:
function getAccountTokenData(
address account,
address token) public view
returns (bool hasPosition,
uint256 balanceOf,
uint256 collateralOrDebtAmount)
address
account
The address of the account to check token data of
address
token
The address of the market token
Return data:
bool
Whether the account has an active position in the token
uint256
The balance of the token that the account holds
uint256
The amount of collateral posted or debt owed by the account
Description: Returns the debt balance of an account based on stored data.
Function signature:
function getAccountDebtData(
address account,
address token) public view returns (uint256)
address
account
The address whose debt balance should be calculated
address
token
The eToken address to check debt for
Return data:
uint256
The account's cached debt balance for the token
Description: Calculates the current utilization rate for an eToken.
Function signature:
function getUtilizationRate(address eToken) public view returns (uint256)
address
eToken
The earning token to pull interest rate data for
Return data:
uint256
The utilization rate, in WAD (18 decimals)
getBorrowRatePerYear()
Description: Returns the current borrow interest rate per year for an eToken.
Function signature:
function getBorrowRatePerYear(address eToken) public view returns (uint256)
address
eToken
The earning token to pull interest rate data for
Return data:
uint256
The borrow interest rate per year, in WAD (18 decimals)
getPredictedBorrowRatePerYear()
Description: Returns predicted upcoming borrow interest rate per year for an eToken.
Function signature:
function getPredictedBorrowRatePerYear(address eToken) public view returns (uint256)
address
eToken
The earning token to pull interest rate data for
Return data:
uint256
The predicted borrow interest rate per year, in WAD (18 decimals)
getSupplyRatePerYear()
Description: Returns the current supply interest rate per year for an eToken.
Function signature:
function getSupplyRatePerYear(address eToken) public view returns (uint256)
address
eToken
The earning token to pull interest rate data for
Return data:
uint256
The supply interest rate per year, in WAD (18 decimals)
Description: Returns the base rewards for a token.
Function signature:
function getBaseRewards(address token) public view returns (uint256)
address
token
The token address to check rewards for
Return data:
uint256
The base rewards for the token
Description: Returns price information for multiple assets.
Function signature:
function getPrices(
address[] calldata assets,
bool[] calldata inUSD,
bool[] calldata getLower) public view
returns (uint256[] memory, uint256[] memory)
address[]
assets
Array of asset addresses to get prices for
bool[]
inUSD
Boolean array indicating whether to return prices in USD
bool[]
getLower
Boolean array indicating whether to get the lower price
Return data:
uint256[]
Array of asset prices
uint256[]
Array of timestamp information for the prices
Description: Checks if a user has unclaimed rewards.
Function signature:
function hasRewards(address user) public view returns (bool)
address
user
The user address to check for rewards
Return data:
bool
Whether the user has rewards to claim
Description: Returns comprehensive data for all markets including market information, eToken data, and pToken data.
Function signature:
function getAllMarketData(address account) public view returns (AllMarketData[] memory)
address
account
The account to get market data for
Return data:
AllMarketData[]
Array of AllMarketData structs containing market data for all markets
Description: Returns detailed data for a specific market.
Function signature:
function getMarketData(
address market,
address account) public view
returns (MarketData memory result)
address
market
The market to get data for
address
account
The account to get market data for
Return data:
MarketData
A struct containing market data including TVL, collateral, lending, borrows, and user position
Description: Returns detailed asset data for a specific market, including eToken and pToken information.
Function signature:
function getMarketAssetData(
address market,
address account) public view
returns (MarketETokenData[] memory, MarketPTokenData[] memory)
address
market
The market to get asset data for
address
account
The account to get asset data for
Return data:
MarketETokenData[]
Array of eToken data for the market
MarketPTokenData[]
Array of pToken data for the market
Description: Determines whether an account can currently be liquidated in a market for specific eToken and pToken.
Function signature:
function flaggedForLiquidation(
address market,
address account,
address eToken,
address pToken) external view returns (bool)
address
market
The market to check for liquidation flag
address
account
The account to check for liquidation flag
address
eToken
The eToken to be repaid during potential liquidation
address
pToken
The pToken to be seized during potential liquidation
Return data:
bool
Whether the account can be liquidated currently
Description: Returns the current TVL inside a Curvance market.
Function signature:
function getMarketTVL(address market) public view returns (uint256 result)
address
market
The market to query TVL for
Return data:
uint256
The current TVL inside the market, in WAD (18 decimals)
Description: Returns the current collateral TVL inside a Curvance market.
Function signature:
function getMarketCollateralTVL(address market) public view returns (uint256 result)
address
market
The market to query collateral TVL for
Return data:
uint256
The current collateral TVL inside the market, in WAD (18 decimals)
getMarketCollateralPostedByUsd()
Description: Returns the USD value of collateral posted in a market.
Function signature:
function getMarketCollateralPostedByUsd(address market) public view
returns (uint256 result)
address
market
The market to query collateral USD value for
Return data:
uint256
The USD value of collateral posted in the market
Description: Returns the current lending TVL inside a Curvance market.
Function signature:
function getMarketLendingTVL(address market) public view returns (uint256 result)
address
market
The market to query lending TVL for
Return data:
uint256
The current lending TVL inside the market, in WAD (18 decimals)
Description: Returns the current outstanding borrows inside a Curvance market.
Function signature:
function getMarketBorrows(address market) public view returns (uint256 result)
address
market
The market to query outstanding borrows for
Return data:
uint256
The current outstanding borrows inside the market, in WAD (18 decimals)
getMarketCollateralAssets()
Description: Returns listed collateral assets inside a market.
Function signature:
function getMarketCollateralAssets(address market) public view
returns (address[] memory)
address
market
The market to query collateral assets for
Return data:
address[]
Array of collateral asset addresses in the market
getMarketDebtAssets()
Description: Returns listed debt assets inside a market.
Function signature:
function getMarketDebtAssets(address market) public view returns (address[] memory)
address
market
The market to query debt assets for
Return data:
address[]
Array of debt asset addresses in the market
getMarketAssets()
Description: Returns all listed market assets inside a market.
Function signature:
function getMarketAssets(address market) public view returns (address[] memory)
address
market
The market to query assets for
Return data:
address[]
Array of all asset addresses in the market
Description: Returns the current TVL inside an MToken token.
Function signature:
function getTokenTVL(address token, bool getLower) public view returns (uint256 result)
address
token
The token to query TVL for
bool
getLower
Whether to get the lower price for TVL calculation
Return data:
uint256
The current TVL inside the token, in WAD (18 decimals)
Description: Returns the outstanding underlying tokens borrowed from an EToken market.
Function signature:
function getTokenBorrows(address token) public view returns (uint256 result)
address
token
The token to query outstanding borrows for
Return data:
uint256
The outstanding underlying tokens, in WAD (18 decimals)
Description: Returns the price of a token.
Function signature:
function getTokenPrice(address token) public view returns (uint256)
address
token
The token to get the price for
Return data:
uint256
The token price
Earn Tokens (eTokens) are Curvance's lending market tokens that enable users to earn interest on deposited assets. eTokens function similarly to interest-bearing tokens in other lending protocols but with a focus on enhanced security and flexibility. Users who deposit underlying assets receive eTokens representing their share of the lending pool, which increase in value as borrowers pay interest.
eTokens operate within a network of contracts:
EToken: The core implementation of the Earn Token functionality.
ETokenWithGauge: Extension that integrates with Curvance's Gauge system for additional rewards.
MarketManager: Manages risk parameters, collateral requirements, and market health.
CentralRegistry: Provides protocol-wide configuration and permissions.
InterestRateModel: Determines interest rates based on market utilization.
eTokens maintain several key state variables:
The exchangeRate
is a critical parameter that determines the conversion between eTokens (shares) and underlying assets. It increases over time as interest accrues, allowing eToken holders to claim more underlying assets for the same number of tokens.
User calls mint()
with underlying assets.
Contract accrues any pending interest.
Contract calculates shares based on current exchange rate.
Underlying tokens are transferred from the user to the contract.
eTokens are minted to the user.
MarketManager is notified of the deposit (if needed).
User calls redeem()
with eTokens.
Contract accrues any pending interest.
Contract calculates assets based on current exchange rate.
Underlying tokens are transferred from the contract to the user.
eTokens are burned.
MarketManager is notified of the withdrawal.
User must first deposit collateral into a pToken.
User calls borrow()
on an eToken.
Contract accrues any pending interest.
MarketManager checks if user has sufficient collateral.
Debt is recorded for the user.
Underlying tokens are transferred to the user.
User calls repay()
or repayFor()
.
Contract accrues any pending interest.
Current debt is calculated.
Underlying tokens are transferred from the user to the contract.
User's debt is reduced or eliminated.
MarketManager is updated regarding the user's position.
Interest accrual is a key feature of eTokens, handled through the accrueInterest()
function:
Time elapsed since last update is calculated.
InterestRateModel determines appropriate interest rate based on market utilization.
Interest is applied to total borrows.
A portion of interest (determined by interestFactor
) is allocated to protocol reserves.
Exchange rate is updated, increasing the value of all eTokens.
This process occurs automatically when users interact with any of the main functions, ensuring up-to-date state before transactions execute.
The exchange rate is calculated by:
exchangeRate = (marketUnderlyingHeld + totalBorrows) * WAD / (totalSupply + totalReserves)
This rate determines how many underlying tokens each eToken is worth and increases over time as interest accrues.
eTokens work closely with the MarketManager, which:
Defines which assets can be used as collateral (pTokens).
Sets borrowing parameters like collateral factor and liquidation threshold.
Authorizes borrowing based on user's collateral.
Manages the liquidation process for underwater positions.
Enforces market-wide caps and security measures.
eTokens incorporate multiple security features:
Reentrancy protection on all critical functions.
Safe variants of functions for external protocol integration.
Accrual of interest before any state-changing operations.
Minimum hold periods to prevent flash loan attacks.
Multiple layer validation for borrowing and collateralization.
The eToken contracts generate protocol revenue through:
Interest Factor: A portion of all interest paid by borrowers goes to protocol reserves.
These reserves can be withdrawn by the DAO using processWithdrawReserves()
.
For eTokens with gauges, additional rewards may be distributed to depositors based on their contribution.
Contract: eToken
Description: Converts an amount of underlying assets to equivalent eToken shares using the current exchange rate.
Function signature:
Return data:
Contract: eToken
Description: Converts an amount of eToken shares to equivalent underlying assets using the current exchange rate.
Function signature:
Return data:
Contract: eToken
Description: Users deposit underlying assets into the market and receive eTokens in return.
Function signature:
Return Data:
Events:
Contract: eToken
Description: Deposits underlying assets into the market, and recipient receives eTokens.
Function signature:
Return data:
Events:
Contract: eToken
Description: Redeems eTokens in exchange for the underlying asset
Function signature:
Return data:
Events:
Contract: eToken
Description: Used by a delegated user to redeem eTokens in exchange for the underlying asset, on behalf of account.
Function signature:
Return data:
Events:
Contract: eToken
Description: Borrows underlying tokens from lenders, based on collateral posted inside this market.
Function signature:
Event:
Contract: eToken
Description: Used by a delegated user to borrow underlying tokens from lenders, based on collateral posted inside this market by account
Function signature:
Event:
Contract: eToken
Description: Repays underlying tokens to lenders, freeing up their collateral posted inside this market and updates interest before executing the repayment.
Function signature:
Event:
Contract: eToken
Description: Repays underlying tokens to lenders, on behalf of account
, freeing up their collateral posted inside this market.
Function signature:
Event:
Interest is accrued on borrowed assets and distributed to eToken holders through the increasing exchange rate. The accrual process is managed by the accrueInterest
function.
Contract: eToken
Function signature:
This function:
Calculates time elapsed since the last interest accrual.
Fetches the current interest rate from the interest rate model.
Computes the interest amount based on current total borrows.
Updates total borrows with the new interest.
Allocates a portion of interest to protocol reserves based on the interestFactor.
Updates the exchange rate to reflect the new value of each eToken.
Interest accrues automaically when users interact with the protocol (mint, redeem, borrow, repay) as these functions call accrueInterest
internally.
Events:
Contract: eToken
Description: Returns the current exchange rate after updating interest, used to determine how many underlying tokens each eToken is worth:
Function signature:
Return data:
Events:
Curvance provides safe versions of key functions with additional reentrancy protection. These should be used when integrating with external protocols to minimize security risks.
Contract: eToken
Description: Updates pending interest and returns the up-to-date exchange rate from the underlying to the eToken, with inherent reentrancy protection.
Function signature:
Return data:
Event:
Contract: eToken
Description: Updates pending interest and returns the up-to-date balance of account
, in underlying assets, with inherent reentrancy protection.
Function signature:
Return data:
Event:
Contract: eToken
Description: Updates pending interest and returns the current debt balance for account
with inherent reentrancy protection.
Function signature:
Return data:
Event:
Contract: eToken
Description: Returns the current debt balance for a given account without accruing interest. This is a gas-efficient view function that uses the cached exchange rate.
Function signature:
Return data:
Contract: eToken
Description: Returns the estimated future debt balance for an account at a specific timestamp, assuming interest rates remain constant.
Function signature:
Return data:
Contract: eToken
Description: Returns the current yearly borrow interest rate for the market, calculated from the interest rate model.
Function signature:
Return data:
Contract: eToken
Description: Returns the current yearly supply interest rate for lenders, derived from the borrow rate and adjusted by the interest factor.
Function signature:
Return data:
// User balances
mapping(address => uint256) public balanceOf;
// Debt tracking
mapping(address => DebtData) internal _debtOf;
// Market state
MarketData public marketData;
uint256 public totalBorrows;
uint256 public totalReserves;
uint256 public totalSupply;
// Structures
// Data for a market.
struct MarketData {
uint40 lastTimestampUpdated;
uint216 exchangeRate;
uint256 compoundRate;
}
// Data for a user's debt.
struct DebtData {
uint256 principal;
uint256 accountExchangeRate;
}
function convertToShares(uint256 amount) public view returns (uint256)
uint256
amount
The number of underlying tokens to theoretically convert to eTokens
uint256
The number of eTokens a user would receive for the given amount of underlying
function convertToAssets(uint256 tokens) public view returns (uint256)
uint256
tokens
The number of eToken shares to theoretically convert to underlying assets
uint256
The number of underlying assets the user would receive for redeeming shares
function mint(uint256 amount) external returns (uint256)
uint256
amount
The amount of the underlying asset to deposit
uint256
The amount of eTokens minted
// From EToken
// Emitted from EToken and underlying asset.
event Transfer(address indexed from, address indexed to, uint256 value);
// Only emitted if interest needs to be approved.
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
function mintFor(uint256 amount, address recipient) external returns (uint256)
uint256
amount
The amount of the underlying asset to deposit
address
recipient
The account that should receive the eTokens
uint256
The amount of eTokens minted
// From EToken
// Emitted from EToken and underlying asset.
event Transfer(address indexed from, address indexed to, uint256 value);
// Only emitted if interest needs to be approved.
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
function redeem(uint256 tokens, address recipient) external returns (uint256)
uint256
tokens
The number of eTokens to redeem for underlying tokens
address
recipient
The account who will receive the underlying assets
uint256
The amount of underlying asset redeemed
// From EToken
// Emitted from EToken and underlying asset.
event Transfer(address indexed from, address indexed to, uint256 value);
// Only emitted if interest needs to be approved.
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
// From MarketManager
// Only emitted if positions need to be closed
event PositionAdjusted(address mToken, address account, bool open);
function redeemFor(
uint256 tokens,
address recipient,
address account) external returns (uint256)
uint256
The amount of underlying asset redeemed
// From EToken
// Emitted from EToken and underlying asset.
event Transfer(address indexed from, address indexed to, uint256 value);
// Only emitted if interest needs to be approved.
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
// From MarketManager
// Only emitted if positions need to be closed
event PositionAdjusted(address mToken, address account, bool open);
function borrow(uint256 amount) external
uint256
amount
The amount of the underlying asset to borrow
// From EToken
event Borrow(address account, uint256 amount);
// Emitted from EToken and underlying ERC20 asset.
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
);
// From MarketManager
// Only emitted if interest needs to be accrued.
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
// 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 PositionAdjusted(address mToken, 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 EToken
event Borrow(address account, uint256 amount);
// Emitted from EToken and underlying ERC20 asset.
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
);
// From MarketManager
// Only emitted if interest needs to be accrued.
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
// 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 PositionAdjusted(address mToken, address account, bool open);
function repay(uint256 amount) external nonReentrant
uint256
amount
The amount of the underlying asset to repay, or 0 for the full outstanding amount
// From EToken
event Repay(address payer, address account, uint256 amount);
// Only emitted if interest needs to be accrued.
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
// 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 PositionAdjusted(address mToken, 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 EToken
event Repay(address payer, address account, uint256 amount);
// Only emitted if interest needs to be accrued.
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
// 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 PositionAdjusted(address mToken, address account, bool open);
function accrueInterest() public
// Only emitted if interest needs to be accrued.
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
function exchangeRateWithUpdate() external returns (uint256)
uint256
Calculated exchange rate, in WAD
.
// Only emitted if interest needs to be accrued.
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
function exchangeRateWithUpdateSafe() public nonReentrant returns (uint256)
uint256
Calculated exchange rate, in WAD
.
// Only emitted if enough time has passed since the last interest accrual
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
function balanceOfUnderlyingSafe(
address account
) external returns (uint256) {
uint256
The amount of underlying owned by account
.
// Only emitted if enough time has passed since the last interest accrual
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
function debtBalanceWithUpdateSafe(
address account
) external nonReentrant returns (uint256)
uint256
The current balance index of account
, with pending interest applied.
// Only emitted if enough time has passed since the last interest accrual
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
function debtBalanceCached(address account) public view returns (uint256)
address
account
The address whose debt balance should be calculated
uint256
The current balance of debt for the account
function debtBalanceAtTimestamp(
address account,
uint256 timestamp) public view returns (uint256)
address
account
The address whose debt balance should be calculated
uint256
timestamp
The unix timestamp to calculate the account's debt balance with
uint256
The estimated debt balance at the specified timestamp
function getBorrowRatePerYear() public view returns (uint256)
uint256
The borrow interest rate per year, in WAD format (1e18)
function getSupplyRatePerYear() public view returns (uint256)
uint256
The supply interest rate per year, in WAD format (1e18)
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.).
Universal Balance introduces a dual-balance accounting model for each user:
Sitting Balance: Tokens held in the contract but not deployed to lending markets.
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.
UniversalBalance: Base implementation for ERC20 tokens.
UniversalBalanceNative: Extension for native gas tokens with wrapping/unwrapping.
EToken: Corresponding lending market token that generates yield.
CentralRegistry: Provides system-wide configuration and permissions.
Universal Balance maintains the following key state variables:
struct UserBalance {
uint256 sittingBalance;
uint256 lentBalance;
}
// User balances tracking
mapping(address => UserBalance) public userBalances;
// Contract configuration
IEToken public immutable linkedToken;
address public immutable underlying;
Each Universal Balance contract is linked to:
A specific underlying token (e.g., USDC).
The corresponding EToken in Curvance (e.g., eUSDC).
User deposits tokens to UniversalBalance via deposit()
or depositFor()
.
User specifies whether to keep as sitting balance or lend it.
If lending is chosen:
Tokens are transferred to the EToken contract via mint()
.
Resulting EToken shares are tracked in the user's lentBalance
.
If keeping as sitting balance:
Tokens are held in the UniversalBalance
contract.
User's sittingBalance
is increased.
User requests withdrawal via withdraw()
or withdrawFor()
.
Contract checks if withdrawal can be fulfilled from sitting balance.
If sitting balance is insufficient:
Required ETokens are redeemed from lending market.
Underlying tokens are received.
Tokens are sent to the recipient.
User's balance (sitting or lent) is reduced accordingly.
User requests to lend sitting balance via lendAssets()
.
Sitting balance is reduced.
Tokens are deployed to lending market.
Lent balance is increased.
User requests to unlend via unlendAssets()
.
Lent balance is reduced.
ETokens are redeemed from lending market.
Sitting balance is increased.
User sends native tokens (ETH) directly to contract.
Native tokens are wrapped (WETH).
Added to user's sitting balance.
User deposits native tokens via depositNative()
.
Similar to standard deposit with auto-wrapping.
User withdraws to native tokens via withdrawNative()
.
Wrapped tokens are unwrapped.
Native tokens are sent to recipient.
Universal Balance contracts interact directly with their linked EToken contracts.
When lending, they call mint()
on the EToken.
When unlending, they call redeem()
on the EToken.
Yield accrues automatically through the EToken's interest model.
UniversalBalanceNative includes special support for oracle funding:
Oracle Manager can request funds via useBalanceForOracleUpdate()
.
User's balance is used to pay for oracle updates.
This enables "pull-based" oracle updates where users can fund oracle operations.
Permission System: Implements delegated operations through the PluginDelegable
contract.
Reentrancy Protection: All critical functions include reentrancy guards.
Non-Custodial Design: Users maintain full control of their assets.
Permission Checks: Actions that affect user balances verify permissions.
Batch Operations: Multi-user functions to reduce gas costs and transaction volume.
Direct Transfers: Transfer portions of balance to other users.
Permission Delegation: Allow third-party operations on your balance.
Batch Operations: Efficient multi-user management.
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.
UniversalBalance interacts with several Curvance contracts:
UniversalBalanceNative adds these interactions:
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.
Description: Deposits underlying token into user's Universal Balance account, either to be held or lent out.
Contract: UniversalBalance
Function signature:
function deposit(uint256 amount, bool willLend) external
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 & EToken
// Potentially emitted multiple times:
// 1. Always emitted when assets are transferred to UniversalBalance
// 2. If willLend is true, transferring underlying asset from UniversalBalance to EToken
// 3. Emitted when ETokens are minted.
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
);
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
Function signature:
function depositFor(uint256 amount, bool willLend, address recipient) external
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 & EToken
// Potentially emitted multiple times:
// 1. Always emitted when assets are transferred to UniversalBalance
// 2. If willLend is true, transferring underlying asset from UniversalBalance to EToken
// 3. Emitted when ETokens are minted.
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
);
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
Function signature:
function multiDepositFor(
uint256 depositSum,
uint256[] calldata amounts,
bool[] calldata willLend,
address[] calldata recipients
) external
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 & EToken
// Potentially emitted multiple times:
// 1. Always emitted when assets are transferred to UniversalBalance
// 2. If willLend is true, transferring underlying asset from UniversalBalance to EToken
// 3. Emitted when ETokens are minted.
event Transfer(address indexed from, address indexed to, uint256 value);
// Only emitted once per transaction.
// Only emitted if interest needs to be accrued.
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
Description: Withdraws underlying token from user's Universal Balance account, currently held or lent out.
Contract: UniversalBalance
Function signature:
function withdraw(
uint256 amount,
bool forceLentRedemption,
address recipient
) external returns (uint256 amountWithdrawn, bool lendingBalanceUsed)
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:
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 EToken & Underlying asset.
// Always emitted from underlying asset when transferring to the recipient.
// If withdrawing from lent balance:
// Emitted from the EToken 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);
// Only emitted if interest needs to be accrued.
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
// From MarketManager
// Potentially emitted if positions need to be closed.
event PositionAdjusted(address mToken, 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)
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:
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 EToken & Underlying asset.
// Always emitted from underlying asset when transferring to the recipient.
// If withdrawing from lent balance:
// Emitted from the EToken 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);
// Only emitted if interest needs to be accrued.
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
// From MarketManager
// Potentially emitted if positions need to be closed.
event PositionAdjusted(address mToken, address account, bool open);
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
Function signature:
function multiWithdrawFor(
uint256[] calldata amounts,
bool[] calldata forceLentRedemption,
address recipient,
address[] calldata owners
) external
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 EToken & Underlying asset.
// Always emitted from underlying asset when transferring to the recipient.
// If withdrawing from lent balance:
// Emitted from the EToken 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);
// Only emitted once per transaction.
// Only emitted if interest needs to be accrued.
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
// From MarketManager
// Potentially emitted if positions need to be closed.
event PositionAdjusted(address mToken, address account, bool open);
Description: Moves a user's Universal Balance between lent and sitting mode.
Contract: UniversalBalance
Function signature:
function shiftBalance(
uint256 amount,
bool fromLent
) external returns (uint256 amountWithdrawn, bool lendingBalanceUsed)
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:
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
)
// 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 EToken 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 PositionAdjusted(address mToken, address account, bool open);
Description: Transfers amount from caller's Universal Balance, currently held or lent out to recipient.
Contract: UniversalBalance
Function signature:
function transfer(
uint256 amount,
bool forceLentRedemption,
bool willLend,
address recipient
) external returns (uint256 amountTransferred, bool lendingBalanceUsed)
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:
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 EToken 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 PositionAdjusted(address mToken, address account, bool open);
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
Function signature:
function transferFor(
uint256 amount,
bool forceLentRedemption,
bool willLend,
address recipient,
address owner
) external returns (uint256 amountTransferred, bool lendingBalanceUsed)
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:
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 EToken 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 PositionAdjusted(address mToken, address account, bool open);
Description: Deposits native gas token into user's Universal Balance account, either to be held or lent out.
Contract: UniversalBalanceNative
Function signature:
function depositNative(bool isLent) external payable
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 eToken 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
);
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
Function signature:
function depositNativeFor(
bool isLent,
address recipient
) external payable
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 EToken
// 1. Emitted if isLent is true
// 2. Emitted from the eToken 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
);
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
Function signature:
function multiDepositNativeFor(
uint256[] calldata amounts,
bool[] calldata willLend,
address[] calldata recipients
) external payable
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 EToken
// 1. Emitted if isLent is true
// 2. Emitted from the eToken when tokens are minted to UniversalBalance
event Transfer(address indexed from, address indexed to, uint256 value);
// Only emitted once per transaction.
// Only emitted if interest needs to be accrued.
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
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
Function signature:
function withdrawNative(
uint256 amount,
bool forceLentRedemption,
address recipient
) external returns (uint256 amountWithdrawn, bool lendingBalanceUsed)
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:
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 EToken
// 1. Emitted when burning tokens during redemption.
// 2. Emitted from the wrapped native asset.
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
);
// From MarketManager
// Potentially emitted if positions need to be closed.
event PositionAdjusted(address mToken, address account, bool open);
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
Function signature:
function withdrawNativeFor(
uint256 amount,
bool forceLentRedemption,
address recipient,
address owner
) external returns (uint256 amountWithdrawn, bool lendingBalanceUsed)
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:
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 EToken
// 1. Emitted when burning tokens during redemption.
// 2. Emitted from the wrapped native asset.
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
);
// From MarketManager
// Potentially emitted if positions need to be closed.
event PositionAdjusted(address mToken, address account, bool open);
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
Function signature:
function multiWithdrawNativeFor(
uint256[] calldata amounts,
bool[] calldata forceLentRedemption,
address recipient,
address[] calldata owners
) external
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
)
// From the wrapped native asset
event Withdrawal(address user, uint256 amount);
// From EToken
// 1. Emitted when burning tokens during redemption.
// 2. Emitted from the wrapped native asset.
event Transfer(address indexed from, address indexed to, uint256 value);
// Only emitted once per transaction.
// Only emitted if interest needs to be accrued.
event InterestAccrued(
uint256 debtAccumulated,
uint256 exchangeRate,
uint256 totalBorrows
);
// From MarketManager
// Potentially emitted if positions need to be closed.
event PositionAdjusted(address mToken, address account, bool open);