The New dCLOB Forex Spot + Perpetual Futures Exchange

Hello Nomads, it has been a monumental 4th quarter of 2024. During Q4, Onomy restructured its leadership, ripped out what did not meet high standards, conducted a rebrand, upgraded the chain to v0.50 of the SDK, built the entire Reserve stablecoin issuance platform, its interface, and launched a successful testnet leading to its currently production-ready status for mainnet launch. All of this was done with zero treasury, funded entirely by remaining founding team like Lalo at TGS Digital and OG Nomads like the X-Chain Validator Alliance (Nomadic, Nomblocks, DeFi Scandinavia) as a strong attempt to revive Onomy to fulfill its potential (Make Onomy Great Again) and overcome challenges rather than give up. We hope that the community appreciates these efforts and understands that what we have accomplished so far on limited resources is extremely rare. In fact, we have been told to “cut losses” back in July/August - but our ambition triumphed over the naysayers (still to this day). It has been nothing but expense for us, but we believe its worth giving everything we’ve got. Cosmos is undergoing a renaissance with new leadership from Interchain Labs (formerly Skip) and the tech stack is in the best place it has ever been for on-chain Forex.

Moving forward, we have completed our research for the architecture of the dCLOB Exchange in our path to become the home of IBC Stableswaps and on-chain Forex.

Development of the exchange will require support on ICS/PSS with Interchain Labs, and support on technical fronts, liquidity support, and ongoing funding. Each of these items are currently being addressed with a proposal made to Interchain Labs (the new steward of Cosmos) themselves - hence the delay on a community update as we look to arrange the support required.

Now, here’s a look at what’s cooking:

dCLOB Exchange Preview Design Documentation

  • Perpetual market
  • Spot market
  • Multiple sub-accounts management

Perpetual Market

Perpetual futures contracts are leveraged trading instruments that enable traders to speculate on asset prices without a fixed expiration date. Unlike traditional futures contracts, perpetual futures remain open indefinitely and are settled in cash, removing the requirement for physical delivery of the underlying asset.

Onomy’s perpetual market will consist of trading pairs between various national currencies based stable coins (minted from Reserve protocol) paired with USD.

Perpetual Market Trading Lifecycle

Funding Payment Mechanism

As perpetual futures contracts never expire, creating a potential disconnect between the contract price and the underlying asset’s spot price. To keep these prices in sync, a mechanism called funding payments is used.

For the exchange module this will be done by a time epoch. At begin block we need to check when the epoch elapsed and execute the payment between longs and shorts. The perpetual market info stores all the params for funding payment process and epoch tracking. And also the cumulative funding and cumulative price for calculating the funding rate and funding payment

eg:

type PerpetualMarketInfo struct {
	// hourly_funding_rate_cap defines the maximum absolute value of the funding rate
	FundingRateCap math.LegacyDec
	// hourly_interest_rate defines the cap value of interest rate
	InterestRate math.LegacyDec
	// next_funding_timestamp defines the next funding timestamp
	NextFundingTimestamp int64
}
type PerpetualMarketFunding struct {
	// cumulative_funding defines the cumulative funding of a perpetual market.
	CumulativeFunding math.LegacyDec
	// cumulative_price defines the cumulative price for the current hour up to the last timestamp
	CumulativePrice math.LegacyDec
	LastTimestamp   int64
}

For every epoch, compute the funding rate as:
fundingRate = twap + hourlyInterestRate

where twap = cumulativePrice / (timeInterval * 24)

with timeInterval = lastTimestamp - startingTimestamp

The cumulativePrice is calculated with every trade. There will be a funding rate cap if the absolute value of computed funding rate is higher than the cap then we will use the funding rate cap but retain the funding rate sign. When the funding rate is positive (contract price is higher than spot price), longs pay shorts. This incentivizes trading activity that could potentially bring the contract price down towards the spot price. And opposite when funding rate is negative.

FundingPayment = FundingRate * MarketPrice. MarketPrice is provided by oracle module.

Market Creation

A market can be created either by the instant launch functionality through a messages MsgInstantPerpetualMarketLaunch which creates a market by paying an extra fee which doesn’t require governance to approve it. Or it is created in the normal way through governance through MsgPerpetualMarketLaunchProposal. Market information can be updated by the market admin by broadcasting MsgUpdatePerpetualMarket transaction.

Order Management

An orderbook will be created for each market containning list of resting orders. This orderbook also in charge of handling orders matching every block.

Initial Margin Requirement

This is the requirement for the initial margin of an order when creating a new position. The idea behind the additional mark price requirement is to minimize the liquidation risk when traded prices and market prices temporally diverge too far from each other. Given the initial margin ratio, an order must fulfill two requirements:

  • The margin must fulfill:Margin ≥ InitialMarginRatio * Price * QuantityFor example: in a market with maximally 20x leverage, the initial
    margin ratio would be 0.05. Any new position will have a margin which is at least 0.05 of its notional.
  • The margin must fulfill the mark price requirement:Margin >= Quantity * (InitialMarginRatio * MarkPrice - PNL)

PNL is the expected profit and loss of the position if it was closed at the current MarkPrice. Solved for MarkPrice this results in:

  • For Buys: MarkPrice ≥ (Margin - Price * Quantity / ((InitialMarginRatio - 1) * Quantity)
  • For Sells: MarkPrice ≥ (Margin + Price * Quantity / ((InitialMarginRatio + 1) * Quantity)

Initial margin ratio is different between markets and is provided when creating the market.

Placing Limit Orders

Limit orders allows a user to specify a maximum price they are willing to pay when buying, or a minimum price they are willing to accept when selling. The order is only executed if the market reaches the specified price (or better).

A user can place a limit buy or sell order by sending a MsgCreatePerpetualLimitOrder. Upon submission, the order can be:

  1. Immediately (fully or partially) matched against other opposing resting orders on the orderbook in the Endblocker batch auction, thus establishing a position for the user.
  2. Added to the orderbook.

Note that in the case of a large order where there is insufficient asset amount it is possible for an order to be partially matched and for the remaining unmatched portion to be added to the
orderbook.

User must provide a portion of the total contract value as initial margin in stable coin as collateral. The list of allowed denoms are stored in the module params and can be updated through
governance proposal.

User cancels a limit buy or sell order by sending a MsgCancelPerpetualOrder which removes the user’s limit order from the orderbook.

Placing Market Orders

Market orders allow user to buy or sell an asset immediately at the best available market price at the batch process during end block. Market orders prioritize execution speed over price control.

If the order is an atomic typed order then the order is execute immediately bypassing the batch execution during end block by paying an extra fees.

User can place a market buy/sell order at market price though MsgCreatePerpetualMarketOrder message. Other order handler logic are the same as limit order ( partial fills )

The order margin collateral are the account available balance.

Position Management

After a market order or limit order matched, a Position will be created for the user. A trader’s position records the conditions under which the trader has entered into the derivative contract and is defined as follows

  • Position Definition:
    • Quantity
    • EntryPrice
    • Margin
    • HoldQuantity
    • CumulativeFundingEntry

As an example, consider the following position in the NomYEN/NomUSD market:

  • Quantity = -2
  • EntryPrice = 0.0064
  • Margin = 800
  • HoldQuantity = 1
  • CumulativeFundingEntry = 34323

This position represents short exposure for 2 contracts of the NomYEN/NomUSD market collateralized with 800 NomUSD, with an entry price of 0.0064. The HoldQuantity represents the quantity of the position that the trader has opposing orders for.

CumulativeFundingEntry represents the cumulative funding value that the position was last updated at. Users can then modify their poisition by:

Increase margin: User submit IncreasePositionMargin message to increase position margin.

Decrease margin: User submit DecreasePositionMargin message to increase position margin.

NewMargin must again be validated with the above mentioned (requirements)

Position Netting

When a new order is matched for a subaccount with an existing position, the new position will be the result from netting the existing position with the new order. A matched order produces a position delta defined by FillQuantity, FillMargin and ClearingPrice. New position state will be
update:

  • Same direction:

Entry Price = (Quantity * EntryPrice + FillQuantity * ClearingPrice) / (Quantity + FillQuantity) Quantity = Quantity + FillQuantity Margin = Margin + FillMargin

  • Opposite direction:

Entry Price no change Quantity = Quantity - FillQuantity Margin = Margin * (Quantity - FillQuantity) / Quantity

Liquidation

When your position falls below the maintenance margin ratio ( defined when
creating the market ), the position can be liquidated by anyone.

For longs:

Margin >= Quantity ∗ MaintenanceMarginRatio ∗ MarketPrice − (MarkPrice−EntryPrice)

For shorts:

Margin >= Quantity ∗ MaintenanceMarginRatio ∗ MarketPrice − (EntryPrice−MarkPrice)

What happens on-chain is that automatically a reduce-only market order of the same size as the position is created. The market order will have a worse price defined as Infinity or 0, implying it will be matched at whatever prices are available in the order book.

The payout from executing the reduce-only market order will not go towards the position owner. Instead, a part of the remaining funds are transferred to the liquidator and the other part is transferred to the insurance fund. The split rate is defined in the module params. If the
payout in the position was negative then the insurance fund will cover the missing funds.

Also note that liquidations are executed immediately in a block before any other order matching occurs.

Spot Market

Spot Market Creation

A market can be instant launched by broadcasting MsgInstantSpotMarketLaunch transaction with an extra listing fee accounted in, this will be transfered to the community pool. Or through governance proposal.

Order Management

User can broadcast MsgBatchCreateSpotLimitOrders or MsgCreateSpotMarketOrder transaction to create a spot limit/market order. To cancel a limit order, use MsgCancelSpotOrder transaction.

Market status

Spot market has four different statuses:

  1. Active
  2. Paused
  3. Suspended
  4. Demolished

Active State

If a spot market is an active state, it can accept orders and trades.

Paused State

If a spot market is a paused state, it will no longer accept orders and trades and will also not allow any users to take actions on that market (no order cancellations).

Suspended State

If a spot market is a suspended state, it will no longer accept orders and trades, and will only allow traders to cancel their orders.

Demolished State

When a market becomes demolished, all outstanding orders are cancelled.

During the beginning block, identifies and handles any spot markets that have been flagged for force-closure. Cancels open orders, updates relevant market data, and sets the market status to paused or closed.

Order Matching and Execution

The order matching and execution process involves the following steps:

  • Match Orders: Match buy and sell orders based on price-time priority. This includes both limit and market orders.
  • Execute Orders: Execute matched
    orders and update user balances accordingly. For buy orders, the quote
    asset is deducted, and the base asset is credited. For sell orders, the
    base asset is deducted, and the quote asset is credited.
  • Update Order Book: Remove executed orders from the order book and update the state to reflect the remaining orders.

Sub-account

Users can manage multiple sub-accounts to allocate funds for specific positions. This feature enables efficient fund management.

Balance Management

Each of the sub accounts has it own balance record which is being manage by the DEX module. Each balance record will refer to a sub-account by the sub-account ID and denom and each record will track the sub-accounts available balance and total balance.

e.g

message Balance {
	SubaccountId string
	Denom        string
	Deposits     *Deposit
}

type Deposit struct {
	AvailableBalance math.LegacyDec
	TotalBalance     math.LegacyDec
}

Operations

Users can update their sub account balance records by:

Deposit

Token will be transferred from user account to the DEX module account. And a
balance records of subaccount will record the amount of money deposited.

To deposit, user can submit a MsgDeposit transaction

MsgDeposit are defined as:

type MsgDeposit struct {
        // user account
	Sender        string
	// subaccount address.
	SubaccountId string
        // deposit amount
	Amount       types.Coin
}

Withdraw

Users can withdraw token from their sub-account to their bank account. This will trigger

  • Checking subaccount balance records for availibility
  • Reduce subaccount balance records for the withdrawed amount
  • Transfer token from DEX module account to user account

To withdraw token, user can submit a MsgWithdraw transaction

MsgWithdraw are defined as:

type MsgWithdraw struct {
        // user address
	Sender       string
	// bytes32 subaccount ID to withdraw funds from
	SubaccountId string
        // withdraw amount
	Amount       types.Coin
}

Sub-account transfer

Users can transfer token between sub-accounts they own. When transferring between sub-accounts, the amount in those account’s balance records will be updated accordingly.

To transfer token between their own sub-accounts, user can submit a MsgSubaccountTransfer transaction. This transaction will check if the sub-account is b

MsgSubaccountTransfer are defined as:

type MsgSubaccountTransfer struct {
        // user address
	Sender                  string
        // sender sub-account address
	SourceSubaccountId      string
        // receiver sub-account address
	DestinationSubaccountId string
        // transfer amount
	Amount                  types.Coin
}

External Transfer

Like Sub-account transfer, users can also transfer token to other users sub-account. This will update those sub-accounts balance records respectively.

To transfer token to other user sub-account, user can submit a MsgExternalTransfer transaction

MsgExternalTransfer are defined as:

type MsgExternalTransfer struct {
        // user address
	Sender                  string
        // sender sub-account address
	SourceSubaccountId      string
        // receiver sub-account address
	DestinationSubaccountId string
        // transfer amount
	Amount                  types.Coin
}

Estimated Development Roadmap & Milestones

1. Milestone 1

Goal :

  • Finalize full documentation
  • Initial design for web-app

Estimated time: 2 weeks

2. Milestone 2

Goal:

  • Implement spot market module and multiple sub-accounts management mechanism
  • Enhance the existing oracle module to support oracle requests from the DEX module
  • Complete the spot market functionality within the web application (UI)

Estimated time: 6 weeks

3. Milestone 3

Goal:

  • Implement perpetual market module and liquidate mechanism
  • Implement funding rate mechanism
  • Complete the perpetual market functionality within the web application (UI)
  • Skip API support for DEX swap and PSM swap

Estimated time: 8 weeks

4. Milestone 4

Goal

  • Upgrade current testnet or launch new testnet for testing features in protocol
  • Get everything ready (code, documents, guide for user) for launching protocol onmainnet

Estimated time : 3 weeks

5. Milestone 5

Goal:

  • Launch protocol on mainnet

Estimated time : 1 week

Please keep in mind that timelines are estimates and heavily dependent on resources available. The community will be made aware when development begins from various DAO contributors.

2 Likes

Thanks for sharing !

Seems like a make or break moment for the Cosmos hub, agree, it could be an opportunity worth exploring to align with this new team, their vision for the hub, and on going initiatives to turn things around : )

1 Like