Orderflow Auction System
Overview
The Curvance orderflow auction system combines off-chain liquidation auctions with on-chain enforcement to enable:
SOON: Dynamic liquidation penalty and close factor per auction.
Priority access via a per‑market auction buffer (10 bps for correlated markets, 50 bps for uncorrelated).
Efficient batch liquidations with cached pricing.
Strict permissions and correctness guards via transient storage.
On-chain logic refers to this as 'auction-based liquidations' and integrates cleanly with any off-chain solver/auction stack (e.g. Atlas/Fastlane).
System Architecture
The Curvance orderflow auction system integrates on-chain and off-chain components to enable efficient liquidations with dynamic liquidation sizes and penalty rates.
Off-Chain
Price monitoring services detect changes.
Auction framework coordinates liquidator bidding.
Bundler constructs a single transaction for the winning sequence of liquidations.
On-Chain
CentralRegistry unlocks the specific market for the current transaction.
MarketManager handles liquidation execution and validates configuration.
Dynamic Penalty System manages incentive and close factor via transient storage.
Auction Buffer System provides priority access to liquidations.
These components work together to process liquidations with optimal penalty rates while maximizing value capture and minimizing bad debt risk.

Key Features
Dynamic Liquidation Penalties: Adjustable within per-token min/max ranges, set per-transaction via transient storage.
Transient Storage: Parameters apply only within the current transaction then reset.
Oracle Integration: Dual-oracle pathing with an isolated pair fetch price during liquidation to prioritize risk mitigation.
Liquidation Buffer: Per‑market advantage, 10 bps (correlated) or 50 bps (uncorrelated).
Efficient Batch Liquidations: Single config/pricing fetch used across multiple liquidations in one transaction.
Auction workflow
Oracle Update & Operation Collection: Price feed updates create liquidation opportunities. Liquidators generate signed UserOps containing bid amounts and execution instructions.
Auction & Bundling: Auctioneer receives bids from liquidators in the form of signed AA (Account Abstraction) operations. The auctioneer then selects highest bidding operations and bundles them into a single atomic transaction with itself as
tx.origin.Transaction Execution: The bundled transaction executes on the Auctioneer Entrypoint contract, which:
Performs pre-execution balance checks.
Calls each liquidator's operation in descending bid order.
Verifies bid payment through post-execution balance checks.
Continues to the next highest bidder if a liquidator fails to pay.
This mechanism maximizes MEV capture while ensuring liquidations always complete in a timely manner.
Core Dataflows
Price Update to Liquidation Flow

The journey from price change to liquidation execution follows these steps:
Price change detected off-chain.
Auction runs and selects winners.
Bundler submits a transaction that:
Unlocks the market in CentralRegistry.
Sets auction parameters + collateral unlock in MarketManager.
Executes batch liquidations.
This auction typically completes within milliseconds off-chain before submitting the transaction.
Liquidation Buffer System
The Auction buffer system provides priority access to liquidations:
Buffer Mechanism: 10 bps buffer for correlated markets or 50 bps buffer for uncorrelated markets. The buffer is applied by multiplying
cSoftandcHardbyAUCTION_BUFFERduring auction liquidations.Implementation: When Auction transactions are detected via transient storage, liquidation health checks apply the buffer and shifts borderline positions into liquidatable range in a narrow window.
Benefits:
Captures liquidations from interest accrual.
Handles LST (Liquid Staking Token) yield accumulation.
Compensates for network latency.
/// @notice Buffer to ensure orderflow auction-based liquidations have
/// priority versus basic liquidations, for correlated assets,
/// in `BPS`.
/// @dev 9990 = 99.9%. Multiplied then divided by `BPS` = 10 bps buffer
/// auction liquidation priority for correlated assets.
uint256 public constant AUCTION_BUFFER_CORRELATED = 9990;
/// @notice Buffer to ensure orderflow auction-based liquidations have
/// priority versus basic liquidations, for uncorrelated assets,
/// in `BPS`.
/// @dev 9950 = 99.5%. Multiplied then divided by `BPS` = 50 bps buffer
/// auction liquidation priority for uncorrelated assets.
uint256 public constant AUCTION_BUFFER_UNCORRELATED = 9950; function _checkLiquidationConfig(
address cToken
) internal view returns (uint256, uint256, uint256) {
(address unlockedCToken, uint256 liqIncentive, uint256 closeFactor) =
getTransientLiquidationConfig();
bool unlockedMarket = centralRegistry.isMarketUnlocked();
bool unlockedCollateral = unlockedCToken == cToken;
if (unlockedMarket || unlockedCollateral) {
// This is an attempted auction liquidation, and is configured
// correctly so give them the auction priority buffer.
if (unlockedMarket && unlockedCollateral) {
return (AUCTION_BUFFER, liqIncentive, closeFactor);
}
// This is an attempted auction liquidation, but its misconfigured
// and unauthorized because of this.
revert MarketManager__UnauthorizedLiquidation();
}
// This is not an attempted auction liquidation, so approve the
// liquidation, but without any offchain liquidation config values.
return (0, 0, 0);
}Dynamic Penalty Dataflow

The dynamic penalty mechanism operates as follows:
Liquidators submit bids indicating desired liquidation incentive (stored as BPS + incentive) and close factor (BPS). Passing 0 for either uses protocol‑derived values. Both are validated against per‑token min/max bounds.
During the auction transaction, the authorized caller:
Unlocks the market in CentralRegistry.
Calls
setTransientLiquidationConfig(cToken, incentive, closeFactor)on MarketManager, which:Validates bounds against token min/max.
Packs collateral address, incentive, and close factor into one transient slot.
Liquidation executes using these transient values.
After the transaction, parameters reset automatically; they can also be explicitly reset mid-bundle if needed.
Per-transaction auction parameters are packed into a single transient word:
[0..159] unlocked collateral cToken address.
[160..175] liquidation incentive (BPS, stored as BPS + incentive).
[176..191] close factor (BPS).
uint256 internal constant _TRANSIENT_LIQUIDATION_CONFIG_KEY
= 0x1966ec4daf81281b2aba49348128e9b155301b8486bde131e0db16a52b730b82;
uint256 internal constant _BITMASK_COLLATERAL_UNLOCKED = (1 << 160) - 1;
uint256 internal constant _BITPOS_LIQ_INCENTIVE = 160;
uint256 internal constant _BITPOS_CLOSE_FACTOR = 176; function setTransientLiquidationConfig(
address cToken,
uint256 incentive,
uint256 closeFactor
) external {
_checkAuctionPermissions();
_checkIsListedToken(cToken);
CurvanceToken memory c = _tokenConfig[cToken];
// Make sure this token actually can be liquidated, by being
// collateralizable in the first place.
if (c.collRatio == 0) {
revert MarketManager__UnauthorizedLiquidation();
}
// Validate `incentive` is within configured incentive bounds, unless
// zero is passed to signal protocol-derived values should be used.
// This also validates `incentive` is not > the 16 bits we have allocated.
if (
incentive != 0 &&
(incentive < c.liqIncMin || incentive > c.liqIncMax)
) {
_revert(_INVALID_PARAMETER_SELECTOR);
}
// Validate `closeFactor` is within configured allowed close factor
// range, unless zero is passed to signal protocol-derived values
// should be used. This also validates `closeFactor` is not > the 16
// bits we have allocated.
if (
closeFactor != 0 &&
(closeFactor < c.closeFactorMin || closeFactor > c.closeFactorMax)
) {
_revert(_INVALID_PARAMETER_SELECTOR);
}
uint256 liqConfig = uint256(uint160(cToken));
assembly {
// Mask `liqConfig` to the lower 160 bits, in case the upper bits
// somehow are not clean.
liqConfig := and(liqConfig, _BITMASK_COLLATERAL_UNLOCKED)
// Equals: liqConfig | (incentive << _BITPOS_LIQ_INCENTIVE) |
// closeFactor << _BITPOS_CLOSE_FACTOR.
liqConfig := or(
liqConfig,
or(
shl(_BITPOS_LIQ_INCENTIVE, incentive),
shl(_BITPOS_CLOSE_FACTOR, closeFactor)
)
)
tstore(_TRANSIENT_LIQUIDATION_CONFIG_KEY, liqConfig)
}
} function resetTransientLiquidationConfig() external {
_checkAuctionPermissions();
/// @solidity memory-safe-assembly
assembly {
tstore(_TRANSIENT_LIQUIDATION_CONFIG_KEY, 0)
}
} function getTransientLiquidationConfig() public view returns (
address cTokenUnlocked,
uint256 incentive,
uint256 closeFactor
) {
uint256 liqConfig;
/// @solidity memory-safe-assembly
assembly {
liqConfig := tload(_TRANSIENT_LIQUIDATION_CONFIG_KEY)
}
cTokenUnlocked = address(uint160(liqConfig));
incentive = uint16(liqConfig >> _BITPOS_LIQ_INCENTIVE);
closeFactor = uint16(liqConfig >> _BITPOS_CLOSE_FACTOR);
}Fallback Mechanism
When auction parameters are not set (transient values are zero), the system uses the base liquidation incentive an interpolated close factor configured by governance, computed from the account's lFactor.
// In _getLiquidationConfig() inside of MarketManagerIsolated.sol
// fallback to base curve when transient is zero.
// We only need to cache these variables if we did not receive close
// factor/liquidation incentive from `getLiquidationConfig`.
if (aData.closeFactor == 0 || aData.liqInc == 0) {
aData.closeFactorBase = c.closeFactorBase;
aData.closeFactorCurve = c.closeFactorCurve;
aData.liqIncBase = c.liqIncBase;
aData.liqIncCurve = c.liqIncCurve;
}// In _canLiquidate() inside of MarketManagerIsolated.sol
// If this liquidation does not have offchain submitted
// parameters then closeFactorCurve will not be 0. We know this since
// closeFactorCurve is BPS - closeFactorBase and closeFactorBase is
// limited to MAX_BASE_CFACTOR meaning closeFactorCurve cannot ever be
// 0 unless we did not receive offchain parameters and we need to
// calculate close factor and liquidation penalty onchain.
if (aData.closeFactorCurve != 0) {
aData.closeFactor = aData.closeFactorBase +
_mulDiv(aData.closeFactorCurve, aData.lFactor, WAD);
aData.liqInc = aData.liqIncBase +
_mulDiv(aData.liqIncCurve, aData.lFactor, WAD);
}Market and Collateral Unlocking Mechanism
Market unlock: performed via CentralRegistry (transient, per-tx).
Collateral unlock + parameters: set in MarketManager (transient, per-tx).
Both market unlock (
CentralRegistry) and cToken unlock (setTransientLiquidationConfig) must be set in‑tx; otherwise, auction attempts revertMarketManager__UnauthorizedLiquidation.
function unlockAuctionForMarket(address marketToUnlock) external {
if (!hasAuctionPermissions[msg.sender]) {
revert CentralRegistry__Unauthorized();
}
// Validate that you're unlocking an approved market manager.
if (!isMarketManager[marketToUnlock]) {
revert CentralRegistry__InvalidParameter();
}
uint256 marketToUnlockUint = uint256(uint160(marketToUnlock));
/// @solidity memory-safe-assembly
assembly {
tstore(_TRANSIENT_MARKET_UNLOCKED_KEY, marketToUnlockUint)
}
} function isMarketUnlocked() public view returns (bool isUnlocked) {
uint256 result;
/// @solidity memory-safe-assembly
assembly {
result := tload(_TRANSIENT_MARKET_UNLOCKED_KEY)
}
// CASE: This is not an Auction tx, so allow all markets,
// and return false, the caller is not approved for auction-based
// liquidations.
if (result == 0) {
return isUnlocked;
}
// True if the caller is approved for auction-based liquidations,
// otherwise false.
isUnlocked = uint256(uint160(msg.sender)) == result;
}Batch Liquidation Support
The system supports efficient multi-liquidation processing:
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 results and returns both seized shares and debt to repay 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.
Auction Mechanism
Auction structure
A brief solver auction is coordinated whenever a liquidation opportunity is detected. Auction-based liquidations are the primary path and have slight priority (~10 bps) versus traditional; they can also backrun oracle updates via Redstone.
The user operation can:
Call
update(oracle, calldata)to push fresh prices via an allowed oracle route, orCall
initiateAuction()to begin an auction without an oracle update (e.g., interest-accrual-triggered events).
An off-chain auction is started with the Atlas SDK; a winner is determined in ~300 ms. The Fastlane bundler executes via account abstraction to the AEE, including the winning bid plus up to 9 fallback bids (max 10).
Auctions are scoped per (collateralToken, marketManager); combined auctions are split/ordered accordingly. During execution, the AEE calls the Curvance Auction Manager to unlock the market/collateral and set transient liquidation parameters (penalty, close factor) within each market's allowed bounds.
Liquidation proceeds only if the solver transfers the native-token bid to the AEE, which forwards it to the Auction Manager as MEV revenue. Multiple auctions can be executed sequentially in a single meta call; transient storage resets at the end of the transaction, relocking auctions until a new auction is initiated.
Solvers bid in the native gas token; Atlas ranks bids by bid amount and attempts the highest first.
On-chain control responsibilities
Validate user operations (sender, target, and allowed entry functions).
Enforce allowed oracle routes for price updates (per-oracle, per-selector allowlist).
For each solver attempt, unlock the market for auction liquidations and activate transient liquidation parameters within market-defined bounds.
Scope auctions per
(collateralToken, marketManager)and reset transient storage at the end of the transaction.On the winning solver, collect the native-token bid and account it as MEV revenue.
Bid Model
Bid components
Solver address: The solver contract that will execute liquidation.
Execution data: Includes liquidation call parameters; the tail of the calldata provides
(collateralToken, marketManager)for pre-solver setup.Bid amount (MEV): A scalar amount in the native gas token used exclusively for ranking.
Signature(s): Signatures proving authenticity (user op and solver op).
Bid ranking
Primary: Higher native-token bid wins.
Ties: Resolved by arrival order/timestamp.
Data Storage Model
The system employs two distinct storage layers:
Permanent Storage:
Penalty ranges (min, max, default values).
Account health statuses.
System configuration parameters.
Transient Storage:
Dynamic penalty values during auction actions.
Close factor values.
Collateral unlock status.
Values automatically reset after transaction completion.
This separation ensures critical data persists while dynamic values remain ephemeral.
Resources:
Atlas Docs: https://atlas-docs.pages.dev/atlas/introduction
Last updated