Perps V3
Last updated
Last updated
Perps v3 is designed for onchain perpetual futures trading of a wide range of assets, on optimistic EVM rollups.
Traders can take the following actions:
Create an account
Manage margin balances
Commit orders
Delegate access
Keepers can take the following actions:
Settle orders committed by traders
Liquidate accounts
Cross margin: account margin can be used across multiple positions on markets
Only async (delayed offchain) orders: async orders seems to be the way forward in general
No order cancellation: Removed one extra action for keepers to perform and LPs to pay for
Role Based Access Control for modifying collateral, opening/closing positions - for full extensibility and composability
Utilization interest rate
Single position per market , and single pending order
Multi collateral: accepts any synths configured in the system as margin for an account
Perps Accounts - Before interacting with the markets traders first create an account, represented as an NFT. An account on Optimism can own many perps accounts, which allows for isolating collateral and opening many positions on a market. Each interaction will take this accountId
as a parameter.
Cross margin - All positions in a perps account are cross-margined against the provided collateral. If the account's collateral drops below their maintenance margin, all positions will be closed and all collateral in the account will be liquidated.
Alternate collateral - Perps accounts can now contain more collateral types than sUSD. These collaterals will increase your available margin based on the latest oracle price. If the total USD value of these collaterals drops below your maintenance margin, all collateral types will be liquidated.
To better understand how to trade perps, see Perps Python SDK
To best understand the process of building a perp trading integration, see the below tests:
Perps v3 configured to use oracle contracts which comply with ERC-7412. Use the client library when building off-chain integrations like UIs and bots.
Call the getMarkets
function to retrieve the list of marketIds
the list of available markets.
Additional market info call be retrieved by calling getMarketSummary
with those market ids and reviewing the MarketCreated
events.
Fetch other market settings using the marketId
using functions like getFundingParameters
and getLiquidationParameters
Call PerpsMarketProxy.createAccount()
to create an account with a random accountId
You can also specify an accountId
, which will revert if the account already exists
Re-using the AccountModule
from v3 core system which comes packaged with RBAC.
The account owner can delegate PERPS_MODIFY_COLLATERAL
role to another address using:
For Base see Base Andromedato handle USDC
Approve spending margin
Deposit margin with ModifyCollateral
For Base see Base Andromedato handle USDC
Each perps account holds assets to use as margin for their positions. Fetch margin balances using these functions on the PerpsMarketProxy
contract:
totalCollateralValue(accountId)
: Get the USD value of all collateral in the account
getAvailableMargin(accountId)
: Get the USD value of the margin available to use as collateral for future positions
getWithdrawableMargin(accountId)
: Get the USD value of the margin you can withdraw immediately
getRequiredMargins(accountId)
: Get USD values of the margin requirements for the specified account, given their open positions
See a sample order commitment transaction here.
Given an account with some available margin, a trader can commit orders to a perps market. That order will be settled by a keeper according to the specified settlementStrategyId
. The Andromeda deployment uses Pyth oracles to settle your order at a future price after a short delay.
Orders are handled asynchronously. Traders submit a commitOrder
transaction, which creates an order to be settled by a keeper.
The commitOrder
function takes a struct containing all of the order information as an argument. You can see an example order commitment here including the arguments to create this struct.
Use 0
as synthMarketId
for snxUSD.
Use any other synth that has a maxCollateralAmount
set by owner of factory to add to account’s margin.
By providing a negative amountDelta
, you are able to withdraw collateral of your choosing.
Note: there are checks in place to ensure you cannot remove more than the required maintenance margin.
Call PerpsMarketProxy.commitOrder(commitment) to commit an order. The input commitment is a tuple configuring the order. Here are some recommendations for those inputs:
marketId
: Call getMarkets()
and metadata
to get more info about the markets
accountId
: The accountId
that has available margin
sizeDelta
: A wei value of the size in units of the asset being traded
settlementStrategyId
: Recommended 0
for Pyth settlement. Call getSettlementStrategy
for more details
acceptablePrice
: Minimum fill price for longs, maximum fill price for shorts
trackingCode
: A bytes32 encoded value for tracking integrator volume
referrer
: An address for configured integrators to receive a share of fees.
sizeDelta
is the change in size of the position. Can determine long/short based on this value.
A settlement strategy is required in order to commit orders. Here’s an example of one that uses a pyth offchain settlement strategy:
See a sample order settlement transaction here.
Orders will typically be settled by a keeper (which you can run - see Perps V3 Keepers) who fetches the price data from Pyth and fills orders for a fee. You can check getOrder(accountId)
for a given account to view the status of the order. The sizeDelta
will be set to 0 when the order is filled, otherwise it will expire and can be replaced with another order.
Events emitted to track orders:
Orders emit OrderCommitted
and OrderSettled
events to track these interactions.
For a sample OrderCommitted
event, review this transaction.
For a sample OrderSettled
event, review this transaction.
See this repo for a subgraph implementation
Is there a method to build a trade preview including fees, fill price, and liquidation prices?
There is no single method for quotes, however there are individual methods for these values.
computeOrderFees
will return the fees and estimated fill price for a given order size.
Liquidation prices are not available directly. Since positions are cross-margined individual position liquidation prices will change depending on the market. Instead, you can fetch the margin requirements using getRequiredMargins
Positions are liquidated when an account's availableMargin
is below the requiredMaintenanceMargin
How can I tell if an account has an open order?
Fetch orders using the getOrder(marketId, accountId)
function.
If the sizeDelta
value is 0 there is no open order. When an order is filled, this value is set to 0.
Other values may be returned when there is not an open order. These values are not reset to reduce gas used during order settlement.
How can I tell if an account has an open position?
Fetch position using the getOpenPosition(marketId, accountId)
function.
The return will include the position size as well as any funding and pnl accrued since the position was opened.
Keepers settle orders after the settlementTime
has been reached. You can get this time from the OrderCommitted
event or by calling getOrder
for an account.
The settle
function reverts with information for retrieving the offchain price data (url
and data
)
The settlePythOrder
function take this price data as well as an argument with encoded accountId
and marketId
parameters. Review this transaction to see sample inputs.
If the order is valid and within settlement window, this function will settle the order and update its accounting of the new position.
More info at Perps V3 Keepers
Call either:
If the account is already flagged for liquidation, the call will proceed with liquidating as much of the account’s position as possible.
If the account is not, it will check if the account is eligible and proceed to liquidate.
Otherwise, the call reverts.
Iterates through all accounts flagged for liquidation and attempts to liquidate.
Gas could be high but so are the rewards 💰
An account is subject to margin requirements as determined by the following values configurable for each market:
When opening a position on a given market, the initial margin requirement is a fraction of the notional value. The fraction is determined by calculating the size’s impact on the skew (position size / skewScale), multiplied by the initialMarginFraction
plus the minimumInitialMarginScalar
.
Once a positive is live, the liquidation threshold is determined by the maintenceMargin
which is the initialMargin requirement multiplied by the configured maintenceMarginScalar
.
To determine if an account is liquidatable, the account’s availableMargin
must be greater than all calculated maintenance margins combined for each market the account has a position open. The maintenance margin also includes the liquidation settlement reward configured per market as a % of the notional size being liquidated.
availableMargin
is determined by subtracting any unrealized pnl to the available collateral margin.
Available functions that will come in handy:
There are some functions you can call initially to determine if an order will go through:
Each market has parameters that dictate how much can be liquidated at any given point.
The equation for max liquidation per second is as follows:
Note: the multiplier is an additional way to limit max liquidation amount per market.
Based on the configured window, maxSecondsInLiquidationWindow
, and maxLiquidationAmountPerSecond
, the max liquidatable amount in any given window is:
maxSecondsInLiquidationWindow * maxLiquidationAmountPerSecond
Example:
In this scenario, a max of 500 ETH
can be liquidated in a 5 second window.
Below functions are permissioned to Synthetix governance
Each proxy is considered to be one “supermarket”, and is initialized with the factory owner as the owner of this supermarket. The supermarket consists of a set of markets that it controls for which cross margin is applied. Each account that’s created is scoped to the supermarket and cannot be used on other supermarkets.
Supermarkets can only be initialized once which registers them with the Core system using the following call (returns the registered market id with core system):
Owner can set other global parameters that apply to all markets:
synthetix-v3/GlobalPerpsMarketConfiguration.sol at main · Synthetixio/synthetix-v3
Two markets have been created on OP Goerli so far:
100
: ETH market
200
: BTC market
If you need the configuration of any of the above created markets, here are the functions you can call:
synthetix-v3/PerpsMarketConfiguration.sol at main · Synthetixio/synthetix-v3
You can always query getSettlementStrategy
to get the details.