This SIP proposes upgrades to Perps v2 to be deployed on Synthetix v3 with significantly improved features, usability, and operational resilience.
This SIP proposes deployment of a perpetual futures market on Synthetix v3. Core upgrades include: (1) native cross-margining of positions, (2) multi-collateral margin support, (3) revamped liquidations, and (4) improved deterministic settlement.
Despite achieving moderate levels of success since launching with ~$150M of open interest and over $20B of trading volume to date, Perps v2 supports a relatively limited set of features (isolated margin / USD margin only). Secondly, order settlements in v2 are fragile and highly susceptible to disruptions in sequencer inclusion latency. Lastly, v2 relies on suboptimal components such as the endorsed liquidator introduced in SIP-2005 which should be phased out in favor of more distributed mechanisms.
Perps v3 proposes to address the challenges described above using several key architectural features including (1) user accounts for aggregated spot and perps market positions, (2) supermarkets for grouped markets with shared LPs, (3) partial liquidations for gradual derisking, (4) retroactive pricing for asynchronously settled orders.
Cross-margin and multi-collateral accounts
At a high level, cross-margin and multi-collateral support are simplified via user accounts represented by an NFT. To ensure homogenous distribution of risk to LPs throughout the cross margin system, individual markets are aggregated into a cross-marginable supermarket.
SIP-2005 introduced endorsed liquidation keepers to perps v2 to ensure that large positions could be liquidated in an MEV-resistant manner (e.g. sandwiching of liquidation transactions). In Perps v3, large positions are gradually liquidated one non-sandwichable portion at a time with a configurable delay in between partial liquidations. This eliminates profit motives to sandwich liquidation transactions, and allows for balancing arbitrages in between partial liquidations.
Delayed orders in Perps v2 have proven extremely effective in minimizing toxic flow, however the details of the implementation can be improved to produce a more resilient settlement flow. In short, the v2 flow requires a price update from after a configurable delay elapses to settle an order. During times of network congestion, this can result in orders being settled with prices from 30-60 seconds post commitment. In perps v3, the settlement process enforces that the execution price is the earliest available valid price (prices with timestamp greater than or equal to
commitment + delay are considered valid)
Once a proxy is created, the first step is to call
initializeFactorywhich will register the super market with the core system. The
superMarketId is then stored and used for future deposits and withdrawals from the core system as debt and credit is accrued.
Each perps supermarket has support for accounts natively. Each account is an NFT and has support for Role Based Access Control. The current permissions that are supported are:
Note: Only the owner of the NFT or an address with permission to perform the above actions on behalf of the owner can perform the action.
Perps v3 supports multiple collateral types. Currently, we only support synths that are registered with the v3 spot market. This is done so that it’s easy for the perps market to have liquidity to sell synths when required, and allows it to easily get quotes to determine margin value.
Each collateral type can be configured by setting the max allowed amount for the supermarket. By setting this value to a non-zero value, the collateral becomes a valid collateral for traders to deposit as margin up-to the max value.
Traders are allowed to open multiple positions for different markets within the supermarket, limited to one position per market. The trader’s account margin value fluctuates based on the PNL fluctuations of each open position.
Traders follow a commit/settle pattern similar to the Spot market to confirm orders. A trader can only have one position per supported market in the supermarket. For an order to be confirmed, it has to pass the following validation checks:
- Trader’s account is not eligible for liquidation already. (See criteria for liquidations in the liquidations section)
- Trader has enough margin in the account, after factoring in all current open position’s PNL, to cover the order fees, the initial margin requirement for the requested position, and any liquidation rewards they might have to pay out in case of liquidation of the position.
Each market can be configured with a set of settlement strategies. These are similar to the ones in the spot market and have all the information for how the orders will settle. The initial supported strategy type is
Pyth, but we’ve future proofed it to add any other oracle provider and strategy, as needed.
A valid order request has the following properties:
uint128 marketId; uint128 accountId; int128 sizeDelta; uint128 settlementStrategyId; // used for slippage protection, in case the market skew produces a fill price that has too far deviated from the trader’s expectation. uint256 acceptablePrice; bytes32 trackingCode; // if configured beforehand, will receive the configured portion of the order fees. address referrer;
When an order is committed, there’s a window in which the order is required to be settled after which the order is considered expired. If the order is expired, only then may a trader commit a new order. If there’s a pending order, the trader is not allowed to modify their margin or commit another order.
The settlement start time is defined as
commitmentTime + settlementDelay, and the end time is
commitmentTime + settlementDelay + settlementDuration. The
settlementDuration are pulled from the settlement strategy. Orders may only be settled while they are within this settlement window as defined above.
A trader must meet certain margin requirements to open new positions, and ensure the account is not eligible for liquidations. The following configurable values are used to determine these aforementioned margins:
uint256 initialMarginRatioD18; uint256 maintenanceMarginScalarD18; uint256 minimumInitialMarginRatioD18; liquidationRewardRatioD18;
To open a new position, the trader must meet an initial margin requirement. The calculation for this requirement is as follows:
impactOnSkew = |size| / skewScale initialMarginRatio = (impactOnSkew * initialMarginRatioD18) + minimumInitialMarginRatioD18
initialMarginRatio is multiplied by the notional value of the requested position to determine the initial margin the trader would require in their account as margin. This validation happens both on order commitment and settlement.
When an account already has positions open, different actions, including
liquidateAccount will check to see whether the trader’s available margin is under the maintenance margin threshold.
Maintenance margin is a factor smaller than the initial margin requirement, and so the calculation is as follows:
maintenaceMarginRatio = initialMarginRatio * maintenanceMarginScalarD18
maintenanceMarginRatio is multiplied by the notional value of the size to get the required maintenance margin for the position. The account is considered eligible for liquidation if the sum of all position’s maintenance margins is greater than the available trader account margin.
There is another margin requirement at play in determining liquidation threshold and that’s the liquidation reward margin. If a position were to be liquidated, the liquidator receives a reward for their service, and that’s determined by the following equation:
liquidationReward = notionalValue * liquidationRewardRatioD18
This reward is added to both the initial and maintenance margins to ensure the trader has enough margin their account to cover the reward cost.
As mentioned in the above section, an account is eligible for liquidation if the account’s available margin is less than the combined maintenance margins of all its positions.
When this criteria is met, a liquidator may call the
liquidateAccount function that will begin the liquidation process:
When an account is flagged for liquidation, the first step is to sell all of their collateral that’s comprised of synths using the spot market, converting them into sUSD, and depositing it back into the core system. This ensures the core system is not holding the trader’s synths as collateral, which can be subject to fluctuations in price.
The account’s positions are then either partially or fully closed. A partial liquidation can happen based on the size of the position and the following configurable parameters for each market:
uint256 maxSecondsInLiquidationWindow; uint256 maxLiquidationLimitAccumulationMultiplier;
The goal of this is to slow down the size of the liquidations happening within a market in a given time window. The size of allowed liquidation for each second is calculated as:
maxLiquidationPerSecond = (makerFee + takerFee) * skewScale * maxLiquidationLimitAccumulationMultiplier; maxLiquidationSize = maxLiquidationPerSecond * maxSecondsInLiquidationWindow;
maxLiquidationSize is the max amount of the market that can be liquidated within the configured window. This mitigates any front-running possibilities where other actors can take advantage of the price impact due to the large swing in skew that results from a large size liquidation.
If an account hits a limit, and can only be partially liquidated, the account stays in the
flaggedForLiquidation array. Liquidation keepers can call the
liquidateAccount after the configured # of seconds in the window, to continue the account liquidation.
liquidateFlagged will iterate over all partially liquidated accounts and try to liquidate more of its positions.
If an account has no open positions, then it’s considered fully liquidated, and is then reset. Once reset, the trader can add new margin and start trading again.
The liquidator receives a reward for liquidating a trader’s account. The logic for the amount of liquidation is as follows:
totalAccumulatedRewards = Σ positionNotionalValue * liquidationRewardRatioD18 globalMinLiquidationReward <= totalAccumulatedRewards <= globalMaxLiquidationReward
- Calculate accumulated rewards by summing all liquidation rewards of all position’s that were liquidated.
- Compare against the global min and max liquidation rewards to determine the final liquidation reward amount that is to be paid out.
The reward is paid out using the account’s margin and is accounted for when calculating the required margins.
Market Debt/Credit System
The supermarket reports debt to the Core system on a continuous basis by calculating the total trader’s PNL across all markets in the supermarket.
When the trader deposits margin, it is automatically deposited into the core system and the supermarket tracks the trader’s collateral amounts. The trader’s total collateral value is added to the reported debt to ensure LPs can’t realize this value until either a trader loses money, or is liquidated. As the system deducts from trader’s accounts the credit is automatically realized by the LPs when their total collateral value is deducted on the supermarket system.
During liquidations, all synths associated to the liquidated account get sold using the spot market and its corresponding value is deposited as credit into the core system in sUSD.
taker fees operate exactly like in Perps v2. Both are configured per market and based on the skew, one of them is applied to the order’s notional value.
Nothing has changed from perps v2 regarding funding. Each position accrues funding and is added to the PNL of a position which is used to determine the trader’s available margin.
Global Perps Market Configuration : applied globally to all markets in the super market
// max amount of collateral trader’s can deposit to use as their margin mapping(uint128 => uint) maxCollateralAmounts; // when deducting from a trader’s account; this priority determines which collateral to deduct from uint128 synthDeductionPriority; // min/max liquidation rewards uint minLiquidationRewardUsd; uint maxLiquidationRewardUsd;
Market Configuration: applied to each specific market
OrderFee.Data orderFees; SettlementStrategy.Data settlementStrategies; uint256 maxMarketSize; // oi cap uint256 maxFundingVelocity; // used for funding uint256 skewScale; // — margin requirement ratios uint256 initialMarginRatioD18; uint256 maintenanceMarginScalarD18; uint256 minimumInitialMarginRatioD18; uint256 liquidationRewardRatioD18; // — // used to determine how much LP $ is locked based on total open interest uint256 lockedOiRatioD18; // — max liquidation amount values uint256 maxLiquidationLimitAccumulationMultiplier; uint256 maxSecondsInLiquidationWindow; // --- // ensures there’s a small minimum margin available as a fixed constant uint256 minimumPositionMargin; // oracle manager feed id to determine price bytes32 feedId;