Spot Market
The Spot Market system generates synths (ERC-20 tokens) and markets that exchange them with stablecoins generated by Synthetix at a price determined by the oracle manager after applying fees.
To execute orders, the markets withdraw or deposit stablecoins with Synthetix (negatively or positively impacting the position of liquidity providers to the market, respectively). The market also reports the total supply of the synths multiplied by their current price to Synthetix as debt, such that the synths are collateralized by liquidity providers in Synthetix. This amount is also locked, meaning that liquidity providers cannot remove delegated credit such that the issued synths would become undercollateralized.
Creating Synths
To create a new synth and a corresponding market, call createSynth(string memory tokenName, string memory tokenSymbol, address marketOwner)
. This function returns a market ID, which can be used in a pool's configuration within Synthetix to provide it with liquidity. The getSynth(uint128 marketId)
function returns the address of the synth deployed for the specified market. The marketOwner
address has the ability to configure price feeds and fees used by the market.
Configuring Prices
The owner of a market may call function updatePriceData(uint128 marketId, bytes32 buyNodeId, bytes32 sellNodeId)
to set the price feeds that should be used by the specified market. The node IDs must first be registered with the oracle manager.
Configuring Synth Market
The owner of the market may set market configurations which includes different fee structures, leverage values and more. See IMarketConfigurationModule interface for a list of configurable values.
Types of Transactions
This market implementation allows synths to be exchanged using three different transaction types: atomic orders, asyncronous orders, and wrapping/unwrapping.
Atomic Orders
The market allows traders to buy or sell synths in a single (atomic) transaction. The relevant logic is included in the Atomic Order Module.
To buy a synth in an atomic order, a trader may call buy(uint128 marketId, uint usdAmount, uint minAmountReceived, address referrer)
with the market ID for the synth they'd like to purchase, the amount of stablecoins they'd like to provide for the exchange, the minimum amount of synths to receive for the transaction to succeed, and an optional referrer
parameter. The trader must first approve the transfer of the amount of stablecoins to the market. There's another convenience function buyExactOut
which can also be used to purchase synths by specifying the exact synth the trader wants in the transaction.
To sell a synth in an atomic order, a trader may call sell(uint128 marketId, uint synthAmount, uint minAmountReceived, address referrer)
with the market ID for the synth they'd like to sell, the amount of synths they'd like to provide for the exchange, the minimum amount of stablecoins to receive for the transaction to succeed, and an optional referrer
parameter. The trader must first approve the transfer of the amount of synths to the market. There's another convenience function sellExactOut
where the trader has the option to specify the amount of USD that'd like to receive instead of the synth they're willing to sell.
Traders also have the option to call quote functions prior to a transaction to estimate cost of transaction with fees. The quote functions are listed below:
Fees
uint atomicFixedFee
- This fee (denominated as a percentage with 18 decimals) is applied to both buy and sell atomic orders.mapping(address => uint) atomicFixedFeeOverrides
- This is a mapping of fees (denominated as a percentage with 18 decimals) that will be used instead ofatomicFixedFee
whenmsg.sender
is found in the mapping.See Additional Fees section below for other applicable fees.
Asyncronous Orders
Asyncronous orders involve two transactions: a commitment and a settlement. This reduces composability, but allows for front-running mitigation such that lower fees can be offered to traders via MarketConfiguration.asyncFixedFee
. Asynchronous orders may only be cancelled prior to settlement if it is outside of the settlement window. The relevant logic is included in the Async Order Module.
The market owner can configure various settlement strategies for asyncronous orders. Each settlement strategy can be defined with the following properties:
Type - There are three types of settlement strategies:
On-chain: Use the price provided by the oracle manager upon settlement
Chainlink: Use Chainlink-verifiable price data related to the settlement time.
Pyth: Use Pyth-verifiable price data related to the settlement time.
Settlement Delay - This is added to the timestamp associated with the block when the commitment is made to determine the settlement time. You must specify a settlement delay greater than 0 to ensure settlements don't happen on the same block as commitment (This is strongly recommended to mitigate front-running for on-chain settlements.)
Settlement Window Duration - The duration after the settlement time at which an order expires. If order has expired, then the order is eligible for cancellation. (This is strongly recommended to mitigate trader optionality for on-chain settlements.)
Price Verification Contract: The price verification contract that will verify the result data blob returned by the offchain gateway during offchain lookup of prices.
Offchain Feed ID: The feedId used to retrieve the off-chain oracle price. This is encoded into the off-chain lookup URL.
Offchain URL gateway: The offchain gateway url that is communicated to the client to call offchain.
Settlement Reward: The reward amount given to the address initiating a settlement on a trader's behalf.
Price Deviation Tolerance - compares the onchain price and the price retrieved from offchain and ensures the tolerance isn't above this configured value.
disabled - Ability to disable the strategy for the corresponding market.
The following actions are involved with asyncronous orders:
Commit
The trader deposits assets (stablecoins if buying or synths if selling) into escrow
A new AsyncOrderClaim is created and the trader is set as the owner.
Settle
While in the settlement window, anyone can settle the order. Upon settlement, the claim is retrieved based on the market and order id specified. Settlement cannot occur if it's outside of the settlement window which is defined as settlementTime + the configured duration. Depending on the type of settlement strategy used for the order, the following can happen:
On-chain: The order is settled the same way an atomic order would except with the price provided by the oracle manager at the time of the settle transaction.
Pyth/Chainlink off-chain: a revert message,
OffchainLookup
, is constructed in accordance with EIP-3668 and thrown once the validity of the claim is determined.
Settling offchain
The aforementioned
OffchainLookup
provides a callback function signature which the client uses to call the actual settlement with the data returned from the specified gateway in the revert message.The data is verified using the
priceVerificationContract
that was configured on the settlement strategy and the order is settled using the offchain verified price.
Cancel
Anyone can cancel an order as long as it is not during the settlement window (settlementTime + configured settlement duration).
All escrowed amounts are returned to the owner of the claim, including the fees.
Fees
The asyncFixedFee
is applied to async orders instead of the atomicFixedFee
.
Note: if a custom fee is set for a given transactor, the custom fee takes precendence over all fixed fees.
Wrapping
Markets which are able to provide collateral directly to Synthetix can issue synths of equivalent value after applying fees. The relevant logic is included in the Wrapper Module.
To wrap collateral and receive a synth, a trader may call wrap(uint128 marketId, uint wrapAmount)
with the market ID relevant to the synth they'd like to wrap and the amount of collateral they'd like to provide for the exchange. The trader must first approve the transfer of the amount of collateral to the market.
To unwrap a synth and receive collateral, a trader may call unwrap(uint128 marketId, uint unwrapAmount)
with the market ID relevant to the synth they'd like to unwrap and the amount of synths they'd like to provide for the exchange. The trader must first approve the transfer of the amount of synths to the market.
Traders may simulate calling these functions with callStatic
to retrieve a quote of how much would have been provided in the exchange.
Fees
int wrapFixedFee
- This fee (denominated as a percentage with 18 decimals) is applied when collateral is wrapped and synths are issued. Note that this fee may be negative.int unwrapFixedFee
- This fee (denominated as a percentage with 18 decimals) is applied when collateral is unwrapped and synths are burned. Note that this fee may be negative.
Additional Fees and Configuration
In addition to the fees specific to transaction types outlined above, the following fees may be applied for different effects. Some or all of the fees may also be captured and distributed by a custom fee collector, rather than returned to liquidity providers directly.
Utilization Rate Fee
A supply target fee helps limit liquidity providers' exposure to price fluctuations of the asset in situations where demand becomes very high. The amount of liquidity provided to a market implies a supply target. This is the ideal maximum amount of synths that the market would issue, where all of the credit capacity provided to the market were being utilized. This could expressed as a utilization rate of 100%.
If the utilization rate exceeds 100%, the market can apply a supply target fee on buy orders for atomic and asyncronous orders. The fee rate (configured as a percentage by the market owner with the setMarketUtilizationFees()
function) is taken of the percentage by which the utilization rate exceeds 100% and applied to the order.
For instance, if a buy order would move the utilization rate from 90% to 120%, only utilization above 100% incurs fee, so the fee rate here would be (100% + 120% / 2) = 110%. Based on this calculation, we apply the configured feeRate to the average utilization above 100%, which in this case is 110%. If the fee rate is set to 0.1%, we would multiple 10 (percentage points above utilization) * 0.1% = 1%.
There is also a configurable value called collateralLeverage
where the market owner can specify to which extent the delegated collateral can be leveraged. When creating a new synth market, the default is set to 1x leverage. When set to 2x, the market utilization % is based on the ratio of total outstanding synth supply to 2x the delegated collateral.
Auto-Rebalancing Skew Fee
An auto-rebalancing skew fee allows a market to use wrapping functionality in such a way that reduces protocol risk. A (positive or negative) fee can be applied to buy and sell orders as a function of market skew, defined as the total supply of synths minus the amount of wrapped collateral. This creates an arbitrage opportunity that should reduce the skew.
The market owner can set the skew scale with the function setMarketSkewScale()
. The skew scale is the amount we divide the current market skew by to determine the fee rate.
The skew fee is calculated in two different ways depending on which asset the trader is providing. When a trader is calling buyExactIn
or sellExactOut
, they are specifying a USD amount that'd either like to buy or exact back on a sell. For these transactions, we use the below formula to determine the synth amount. This equation ensures that both when a trader buys and sells the same amount, we end up at zero.
calculateSkew
equation:
For buyExactOut
and sellExactIn
, we know the synth amount the user is requested, or is willing to sell, so the skew calculation boils down to just adding or subtracting the synth amount and averaging the skew for before and after the trade. Ex:
This example shows a buy transaction, the same eq applies but after skew is a subtraction and we use the negative value of skew fee since it's a sell to get the correct skew fee %.
Custom Fee Collector
The owner of a market can deploy a custom fee collector contract (which conforms to the IFeeCollector interface) and attach it to their market with the setFeeCollector()
function.
When a custom fee collector has been set, after each transaction, the market will approve the value of the collected fees (as stablecoins) to be used by the fee collector contract and then call collectFees()
on it. Any fees not transferred out of the market by this call will be deposited to liquidity providers in Synthetix per usual.
Referral Fees
Market owner can set different addresses (referrers) to receive a portion (or all) of the exchange (fixed: async/atomic) fees. Once a referrer is set, on any trade, via the optional referrer
parameter, the trader or trade initiator can specify a referrer and a portion of the fees get sent to the referrer during the transaction. Only exchange fees are eligible for referrers.
Interest Rate
The owner of a market can set an interest rate (denominated as an annual percentage with 18 decimal places) for the synths issued by their market with the setInterestRate()
function. This is implemented by making the synth a rebase token, where the total supply (and holders' individual balances) automatically decay at the specified rate.
Though synth decay may complicate composability, this incentivizes liquidity providers to back synths that tend to be held over long periods of time rather than actively exchanged.
Last updated