# Welcome to Ondo Perps
Source: https://docs.ondoperps.xyz/about
Ondo Perps is a platform for perpetual futures on equities, indices, and commodities with up to 20x leverage and 24/7 trading. The first-of-its-kind permissionless platform is designed to unlock liquidity, execution speed, and capital efficiency that rivals traditional derivatives exchanges. It is powered by Ondo Finance technology, which also powers the institutional-grade infrastructure behind the world's largest tokenized stocks platform, Ondo Global Markets. Accessible worldwide outside the U.S., Panama and other prohibited jurisdictions. Made available by Ondo Global (Panama) Inc.
## Built for Best-In-Class Liquidity
Liquidity is structurally constrained on other RWA perp platforms due to stablecoin-only collateral. Market makers have to double-collateralize their positions when running a delta-neutral strategy, posting stablecoin as collateral on the perp venue and maintaining separate cash and inventory at traditional brokerages. Locking up capital twice across multiple venues for the same economic exposure is highly capital inefficient and introduces operational risks, resulting in market makers quoting smaller size and wider spreads to compensate for the capital costs and embedded risks. This leads to thin orderbooks with limited liquidity for all traders.
Ondo Perps eliminates this constraint by supporting tokenized securities as collateral alongside stablecoins, allowing market makers for the first time to deploy a unified capital base across spot and perpetual positions and quote liquidity based on the real market depth of underlying traditional markets. For example, a market maker can quote NVDA perp positions and hedge within the same system by holding tokenized NVDAon. The result is an RWA perps venue that is substantially more capital efficient and with significantly deeper liquidity than existing platforms.
## The Trader Experience
Orderbook depth reflects actual market maker capital efficiency, not the size of a stablecoin pool. Spreads and slippage on Ondo Perps scale with the depth of the underlying market.
Fund with USDC or supported Ondo tokenized equities. There is no requirement to convert holdings into stablecoins before trading.
Spot and perpetual exposure coexist on the same venue. Hedging a tokenized equity holding is as simple as opening the opposite perps position in the same underlying asset on a single venue. No more fragmented hedging across multiple venues.
Positions can be opened and closed at any time, including nights, weekends, and U.S. public holidays. See [Weekend Mechanics](/weekend-mechanics) for how funding and mark prices behave when underlying markets are closed.
## Get Started
Connect a wallet or sign in with email and start trading in minutes.
Deposit USDC or tokenized stocks as collateral to back your positions.
Walk through a market, limit, or TWAP order step by step.
See every perpetual market currently available.
# Getting started
Source: https://docs.ondoperps.xyz/account-set-up
Ondo Perps supports sign-in through a web3 wallet or email. Sign in at [Ondo Perps](https://app.ondoperps.xyz/) and fund with a supported asset to begin trading. For fully funded purchases of GM tokens, see [Spot trading](/spot-trading).
### **Supported Wallets**
Ondo Perps supports a range of self-custody wallets. Connect using any of the following:
* Coinbase Wallet
* Brave Wallet
* Phantom
* MetaMask
* OKX Wallet
* Nightly
* WalletConnect-compatible wallets
**Eligibility**
Ondo Perps is available globally to eligible users outside the U.S., Panama and other prohibited jurisdictions. Persons and residents of the U.S. and other prohibited jurisdictions are not eligible to use the platform. For more information, review our Terms of Service.
# Get Account
Source: https://docs.ondoperps.xyz/api-reference/account/get-account
/api-reference/rest-spec.json get /v1/account
Returns account information for the authenticated user.
# Get Open Order Counts
Source: https://docs.ondoperps.xyz/api-reference/account/get-open-order-counts
/api-reference/rest-spec.json get /v1/counts/orders
Returns open order counts per market/product for the authenticated account.
# Create API Key
Source: https://docs.ondoperps.xyz/api-reference/api-keys/create-api-key
/api-reference/rest-spec.json post /v1/api_keys
Create a new API key. The secret is returned only once.
# Delete API Key
Source: https://docs.ondoperps.xyz/api-reference/api-keys/delete-api-key
/api-reference/rest-spec.json delete /v1/api_keys/{apiKeyID}
Delete an API key by ID.
# List API Keys
Source: https://docs.ondoperps.xyz/api-reference/api-keys/list-api-keys
/api-reference/rest-spec.json get /v1/api_keys
Returns API keys for the authenticated account.
# Remove API Key IP Whitelist Entry
Source: https://docs.ondoperps.xyz/api-reference/api-keys/remove-api-key-ip-whitelist-entry
/api-reference/rest-spec.json delete /v1/api_keys/{apiKeyID}/ip_whitelist
Remove an IP address from the whitelist for an API key.
# Set API Key IP Whitelist
Source: https://docs.ondoperps.xyz/api-reference/api-keys/set-api-key-ip-whitelist
/api-reference/rest-spec.json post /v1/api_keys/{apiKeyID}/ip_whitelist
Set or update IP whitelist for an API key.
# API Key Authentication
Source: https://docs.ondoperps.xyz/api-reference/api_key_authentication
Example of how to use API keys
## Generating an API Key
To generate an API Key
1. Sign into your account with a web browser
2. Click on your address in the upper right corner
3. Click **API Keys**
4. Click **Add New API Key**
5. Name the key
6. Select the correct permissions
7. Click **Create API Key**
8. Record the secret key
The secret key should be treated the same way as a password and not stored in an unencrypted manner. Place it in a password manager or key vault.
### IP Whitelisting
It is strongly recommended to whitelist the IP addresses which are allowed to make requests for the given API key.
Up to 16 IP addresses can be added per API key. Only IPv4 addresses are supported at this time.
The default state with no whitelisted addresses (empty whitelist) is to allow requests from any IP. If there are addresses on the whitelist, then only requests from the whitelisted IPs are allowed. Requests from IPs not on the whitelist will error with code 401 and message: `"IP addr x.x.x.x is not allowed for key ondoKeyId_yyyyyyyy"`.
## Making an Authenticated REST Request
Authenticated REST requests need the following headers:
* `ONDO-KEY-ID` The key id (including the "ondoKeyId\_" prefix)
* `ONDO-TIMESTAMP` Number of milliseconds since the Unix epoch, this may be at most 10 seconds behind or 1 second ahead of server time.
* `ONDO-SIGN` hex representation of SHA256 HMAC of the following four strings concatenated together using the API Secret (including the "ondoApiSecret\_" prefix).
* `timestamp` identical to the ONDO-TIMESTAMP header
* `method` HTTP method in uppercase
* `requestPath` full path and query parameters of the URL, excluding the hostname
* `body` request body string
## Error Codes
Requests made through API key authentication can fail with the following error codes:
* `api_key_not_found` — The API key provided could not be found.
* `failed_to_parse_timestamp` — The timestamp provided could not be parsed.
* `timestamp_too_far` — The timestamp is too far from the server timestamp (more than 10 seconds behind or 1 second ahead of server time)
* `failed_to_decode_hex_signature` — The hex signature could not be decoded.
* `signature_mismatch` — There is a mismatch in the signature.
* `key_doesnt_have_scope` — The API key does not have the required scope for the endpoint.
* `ip_not_permitted` — The IP address making the request is not whitelisted for this API key.
## Example in Go
```go theme={null}
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"strings"
"time"
)
func main() {
keyId := "ondoKeyId_KEYID"
apiSecret := "ondoApiSecret_SECRET"
method := "GET"
base := "https://api.ondoperps.xyz"
path := "/v1/perps/orders?market=AAPL-USD.P&limit=1000"
body := ""
req, _ := http.NewRequest(method, base+path, strings.NewReader(body))
timestamp := fmt.Sprintf("%d", time.Now().UnixMilli())
// Ensure path contains query params `?market=AVAX-USDC&limit=1000`.
concattedString := timestamp + method + path + body
mac := hmac.New(sha256.New, []byte(apiSecret))
mac.Write([]byte(concattedString))
sig := mac.Sum(nil)
req.Header.Set("ONDO-KEY-ID", keyId)
req.Header.Set("ONDO-TIMESTAMP", timestamp)
req.Header.Set("ONDO-SIGN", hex.EncodeToString(sig))
res, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Printf("http err: %s", err)
return
}
data, _ := io.ReadAll(res.Body)
fmt.Printf("Status: %s, Body: %s", res.Status, data)
}
```
Note: in Go, `req.URL.RequestURI()` contains the query parameters and should be used, whereas `req.URL.Path` does not.
## Example in Python
```python theme={null}
import hmac
import hashlib
import time
import requests
key_id = "ondoKeyId_KEYID"
api_secret = "ondoApiSecret_SECRET"
method = "GET"
base = "https://api.ondoperps.xyz"
path = "/v1/perps/orders?market=AAPL-USD.P&limit=1000"
body = ""
url = base + path
timestamp = str(int(time.time() * 1000))
# Ensure path contains query params `?market=AAPL-USD.P&limit=1000`
concatted_string = timestamp + method + path + body
mac = hmac.new(api_secret.encode(), concatted_string.encode(), hashlib.sha256)
signature = mac.hexdigest()
headers = {
"ONDO-KEY-ID": key_id,
"ONDO-TIMESTAMP": timestamp,
"ONDO-SIGN": signature
}
response = requests.get(url, headers=headers)
print(f"Status: {response.status_code}, Body: {response.text}")
```
# Complete SIWE Address Book Challenge
Source: https://docs.ondoperps.xyz/api-reference/auth/complete-siwe-address-book-challenge
/api-reference/rest-spec.json post /v1/auth/erc-4361/address_book/complete_challenge
Complete the signed challenge to add a withdrawal address.
# Complete SIWE Login
Source: https://docs.ondoperps.xyz/api-reference/auth/complete-siwe-login
/api-reference/rest-spec.json post /v1/auth/erc-4361/login/complete_challenge
Complete Sign-In with Ethereum by submitting the signed challenge. Returns JWT.
# Get SIWE Address Book Challenge
Source: https://docs.ondoperps.xyz/api-reference/auth/get-siwe-address-book-challenge
/api-reference/rest-spec.json post /v1/auth/erc-4361/address_book/get_challenge
Get a Sign-In with Ethereum challenge for adding a withdrawal address.
# Get SIWE Login Challenge
Source: https://docs.ondoperps.xyz/api-reference/auth/get-siwe-login-challenge
/api-reference/rest-spec.json post /v1/auth/erc-4361/login/get_challenge
Get a Sign-In with Ethereum (ERC-4361) challenge for login.
# Invalidate JWT
Source: https://docs.ondoperps.xyz/api-reference/auth/invalidate-jwt
/api-reference/rest-spec.json get /v1/auth/invalidate_jwt
Invalidates all JWTs for the authenticated account.
# Get Builder Code Statistics
Source: https://docs.ondoperps.xyz/api-reference/builder-code/get-builder-code-statistics
/api-reference/rest-spec.json get /v1/builder_code/stats
Returns trading volume and builder fees attributed to the builder code owned by the authenticated account. Time bounds are inclusive. Omit endTime for live statistics; live results use a five-minute start-time bucket and are cached for one hour. Historical requests include endTime and are cached for 24 hours. An explicit endTime must be at least one minute in the past.
# Get Recent Builder Code Fills
Source: https://docs.ondoperps.xyz/api-reference/builder-code/get-recent-builder-code-fills
/api-reference/rest-spec.json get /v1/builder_code/fills
Returns the most recent fills attributed to the builder code owned by the authenticated account, newest first, across every account that traded with that code. The exchange retains at most 200 fills per builder code.
Use this endpoint to resynchronize after a `builderFills` WebSocket disconnect: reconnect, subscribe, then call this endpoint and de-duplicate by `fillId`. It is a short recovery window, not a fill history. For totals over an arbitrary period, use the builder-code statistics endpoint instead.
The retained window is rebuilt when the exchange restarts, so a response can contain fewer than 200 fills shortly afterwards.
# Connect
Source: https://docs.ondoperps.xyz/api-reference/connection/connect
/api-reference/ws-spec.json get /ws
Establish a WebSocket connection. After connecting, send JSON messages to interact with the API.
# Login
Source: https://docs.ondoperps.xyz/api-reference/connection/login
/api-reference/ws-spec.json post /ws/login
Authenticate the WebSocket connection. Required before subscribing to private channels.
**JWT login**: provide `args.token`.
**API key login**: provide `args.key`, `args.time` (Unix milliseconds), and `args.sign` (HMAC-SHA256 of `time + "ondo_perps_ws_login"` using your API secret, hex-encoded).
# Ping
Source: https://docs.ondoperps.xyz/api-reference/connection/ping
/api-reference/ws-spec.json post /ws
Send `{"op": "ping"}` to keep the connection alive. The server responds with `{"type": "pong"}`.
# Get Fills
Source: https://docs.ondoperps.xyz/api-reference/fills/get-fills
/api-reference/rest-spec.json get /v1/perps/fills
Returns a paginated list of fills for the authenticated account.
# Get Fills by Order ID
Source: https://docs.ondoperps.xyz/api-reference/fills/get-fills-by-order-id
/api-reference/rest-spec.json get /v1/perps/orders/{orderID}/fills
Returns all fills for a given order. The `orderID` path parameter can be:
- An internal order ID (e.g. `197ec08e001658690721be129e7fa595`).
- A client order ID prefixed with `client:` (e.g. `client:abc123`).
# Get Fills CSV
Source: https://docs.ondoperps.xyz/api-reference/fills/get-fills-csv
/api-reference/rest-spec.json get /v1/perps/fills/csv
Export fills as CSV, sorted reverse chronologically.
# Get Funding Fee Payments
Source: https://docs.ondoperps.xyz/api-reference/funding-rate/get-funding-fee-payments
/api-reference/rest-spec.json get /v1/perps/funding_fees
Get a paginated history of funding fee payments for the authenticated account. For how funding is calculated and applied, see [Funding rates](/funding-rates).
# Get Funding Rate History
Source: https://docs.ondoperps.xyz/api-reference/funding-rate/get-funding-rate-history
/api-reference/rest-spec.json get /v1/perps/funding_rate_history
Get the historical funding rates for a market. For how funding is calculated and applied, see [Funding rates](/funding-rates).
# Get Funding Rates
Source: https://docs.ondoperps.xyz/api-reference/funding-rate/get-funding-rates
/api-reference/rest-spec.json get /v1/perps/funding_rates
Get an estimate of the current-interval funding rate in a given market, as well as when the next funding is and what premium index measurements make up the funding rate. For how funding is calculated and applied, see [Funding rates](/funding-rates).
# Get Mark Prices
Source: https://docs.ondoperps.xyz/api-reference/funding-rate/get-mark-prices
/api-reference/rest-spec.json get /v1/perps/mark_prices
Get the current mark prices for all perpetual futures markets. For how mark prices are derived, see [External pricing derivations](/pricing-derivations).
# Builder Integration Guide
Source: https://docs.ondoperps.xyz/api-reference/integration_guide
Walk-through integrating with Ondo Perps as a builder. This guide uses JavaScript in-browser examples
> **Full API reference:** [Ondoperps API Documentation](https://ondoperps.mintlify.app/)
> All endpoints in this guide use the **sandbox** environment. For production, swap the base URL.
Ondo Perps is a tokenized equity perps exchange. Builders who integrate Ondo Perps into their frontend earn incremental fees on every fill they route. Fees are deposited directly into your Ondo Perps margin account.
Your app handles the full user onboarding flow: wallet connection, SIWE auth, deposit, and order placement. Once a user is onboarded, every subsequent order is a single REST call with your builder code attached.
### How It Works
The user's wallet touches two things: SIWE login (to prove identity) and the on-chain deposit. Everything after that is REST calls authenticated by a JWT. Your app manages the session, and orders include your builder code. Incremental fees are attributed automatically on each fill.
### Integration Flow
```text theme={null}
1. Connect wallet → User approves wallet connection
2. SIWE auth → User signs challenge, your app gets a JWT
3. Accept TOS → First login only
4. Deposit USDC → On-chain transfer (production only; sandbox uses demo funds)
5. Place orders → REST call with builderCode on every order
6. Earn fees → Incremental fees credited per fill
```
***
## Step 1: Prerequisites
**Environment:** This guide runs in a browser with an EVM wallet extension installed (MetaMask, Phantom EVM, WalletConnect, etc.). The code uses `window.ethereum`.
**Sandbox access:**
1. Log in at [app.ondoperps-sandbox.xyz](http://app.ondoperps-sandbox.xyz) and get your account ID
2. Contact the Ondo Perps team at [**builders@ondoperps.xyz**](mailto:builders@ondoperps.xyz) with your app's public URL (for CORS allowlisting) and your account ID. Fee rate is not configured centrally for your builder code — set your desired fee rate explicitly on each order/stop\_order request via `builderCode.feeRateBpsFractional` (see below); if it's omitted, that fill collects no builder commission. Please note that builders are currently capped at 10 bps fee/order
3. You'll receive an invite code and your builder code
4. The team enables API key management for your account
5. Create and manage your API keys from the frontend
**Base URLs:**
| Environment | REST API | WebSocket |
| - | - | - |
| Sandbox | `https://api.ondoperps-sandbox.xyz` | `wss://api.ondoperps-sandbox.xyz/ws` |
| Production | `https://api.ondoperps.xyz` | `wss://api.ondoperps.xyz/ws` |
***
## Step 2: Set Up Your Client
```jsx theme={null}
const provider = window.ethereum
// Sandbox base URL. For production, use 'api.ondoperps.xyz'
const ondoBase = 'api.ondoperps-sandbox.xyz'
const ondoUrl = `https://${ondoBase}`
// Market to test. Format: {TICKER}-USD.P
// Available markets include: XAU-USD.P, NVDA-USD.P, QQQ-USD.P, AMD-USD.P
const testMarket = 'XAU'
```
### Helper Methods
All authenticated endpoints require `Authorization: Bearer {jwtToken}`. Define these helpers once.
```jsx theme={null}
const parseResponse = (data) => {
if (!data.success) {
throw new Error(`Request failed: ${JSON.stringify(data)} (code: ${data.error_code})`)
}
return data.result
}
const fetchPost = async (endpoint, data) => {
return fetch(`${ondoUrl}${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
.then(response => response.json())
.then(data => parseResponse(data))
}
let jwtToken = ''
const fetchAuthPost = async (endpoint, data) => {
return fetch(`${ondoUrl}${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${jwtToken}`,
},
body: JSON.stringify(data),
})
.then(response => response.json())
.then(data => parseResponse(data))
}
const fetchAuthGet = async (endpoint) => {
return fetch(`${ondoUrl}${endpoint}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${jwtToken}`,
},
})
.then(response => response.json())
.then(data => parseResponse(data))
}
```
***
## Step 3: Authenticate (SIWE)
Ondo Perps uses Sign In With Ethereum ([ERC-4361](https://eips.ethereum.org/EIPS/eip-4361)). The user signs a challenge with their wallet. Your app receives a JWT for all subsequent requests.
### 3a. Connect Wallet
Auth must happen on Ethereum mainnet, regardless of which chain the user deposits on later.
```jsx theme={null}
// Ensure wallet is on Ethereum mainnet
let chainId = await ethereum.request({ method: 'eth_chainId' })
if (chainId !== '0x1') {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x1' }],
})
}
// Request the user's account
const accounts = await provider.request({ method: 'eth_requestAccounts' })
const from = accounts[0]
```
### 3b. Define the SIWE Signing Function
```jsx theme={null}
const siweSign = async (siweMessage) => {
const bytes = new TextEncoder().encode(siweMessage)
const hex = Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('')
const msg = `0x${hex}`
return await provider.request({
method: 'personal_sign',
params: [msg, from],
})
}
```
### 3c. Request and Sign the Challenge
```jsx theme={null}
const getChallengeData = {
walletAddress: from,
chainId: '1',
}
const challenge = await fetchPost('/v1/auth/erc-4361/login/get_challenge', getChallengeData)
const signedMessage = await siweSign(challenge.message)
```
The response includes `challenge.id` and `challenge.message`, which you pass to the next two steps.
### 3d. Complete the Challenge and Get Your JWT
```jsx theme={null}
const completeChallengeData = {
id: challenge.id,
signature: signedMessage
}
const completeChallenge = await fetchPost('/v1/auth/erc-4361/login/complete_challenge', completeChallengeData)
jwtToken = completeChallenge.token
```
### 3e. Accept Terms (First Login Only)
```jsx theme={null}
await fetchAuthPost('/v1/agreement', {
termsVersion: 1,
privacyVersion: 1,
})
```
***
## Step 4: Deposit USDC
### Sandbox
In sandbox, regular deposit flow is optional. You can click through the deposit flow in the [sandbox frontend](https://app.ondoperps-sandbox.xyz). No real funds are needed. Once your account is funded, skip to Step 5: Place Orders.
### Production
Alternatively, use production-like deposit flow, where users deposit USDC on-chain. The flow: request a deposit address from the API, then send an ERC-20 transfer to that address.
Note: this example is written for Sepolia USDC contract, you should use Mainnet USDC contract for Production.
```jsx theme={null}
// Get your account ID
const accountData = await fetchAuthGet('/v1/account')
const accountID = accountData.accountID
```
```jsx theme={null}
// Request a deposit address
const provisionAddressReq = {
symbol: 'USDC',
deposit_destination: {
id: accountID,
wallet: 'margin',
},
network: 'ethereum', // also supports: solana, avalanche
}
const provisioned = await fetchAuthPost('/v1/provision_address', provisionAddressReq)
const depositAddress = provisioned.address
```
```jsx theme={null}
// Switch to the deposit chain (Sepolia for testnet, mainnet for production)
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0xAA36A7' }], // Sepolia testnet
})
// Send ERC-20 transfer: 50 USDC to the provisioned deposit address
// 0xa9059cbb = transfer(address,uint256) selector
// 0x02faf080 = 50,000,000 (50 USDC with 6 decimals)
const depositNoPrefix = depositAddress.replace(/^0x/i, '')
const sent_tx = await window.ethereum.request({
method: 'eth_sendTransaction',
params: [{
to: '0x94a9D9AC8a22534E3FaCa9F4e7F2E2cf85d5E4C8', // Sepolia USDC contract
from: from,
data: `0xa9059cbb000000000000000000000000${depositNoPrefix}0000000000000000000000000000000000000000000000000000000002faf080`,
}],
})
// Poll for transaction confirmation
for (;;) {
const tx = await window.ethereum.request({
method: 'eth_getTransactionByHash',
params: [sent_tx],
})
if (tx.blockHash && tx.blockHash !== '') {
break
}
// Wait 2 seconds between polls to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 2000))
}
console.log('Deposit confirmed on-chain')
```
**Test funds (Sepolia):**
* USDC faucet: [gho.aave.com/faucet](http://gho.aave.com/faucet)
* ETH faucet: [sepolia-faucet.pk910.de](http://sepolia-faucet.pk910.de)
***
## Step 5: Place Orders
Market names use the format `{TICKER}-USD.P`. Examples: `QQQ-USD.P`, `NVDA-USD.P`, `AMD-USD.P`.
See the [Perps REST API](https://www.notion.so/Perps-REST-API-2ff278059c7f8199b12ef2f7e04e59ca?pvs=21) for the full order schema, batch orders, and cancellation.
### Market Order
```jsx theme={null}
// Set builderCode.feeRateBpsFractional to the fee you want to charge on this order
// (in bps, fractional values allowed, e.g. 2.5). There is no centrally configured
// default rate for your builder code — if this is omitted, the fill collects no
// builder commission. Takes precedence over feeRateBps if both are set.
// feeRateBps (integer bps) is deprecated; kept for backwards compatibility.
const marketOrderReq = {
market: `${testMarket}-USD.P`,
type: 'market',
side: 'sell',
size: '0.01',
builderCode: {
feeRateBpsFractional: 2.5,
code: 'yourCode'
}
}
const placedMarketOrder = await fetchAuthPost('/v1/perps/orders', marketOrderReq)
```
> **TP/SL builder attribution:** If this order sets `takeProfit`/`stopLoss`, the same `builderCode` is inherited by the resulting closing fill when the TP or SL triggers.
The response includes `orderId`, `market`, `type`, `side`, `size`, `status`, and `createdAt`. See the [Perps REST API](https://www.notion.so/Perps-REST-API-2ff278059c7f8199b12ef2f7e04e59ca?pvs=21) for the full response schema.
### Check Order Status
```jsx theme={null}
const orderDetails = await fetchAuthGet(`/v1/perps/orders/${placedMarketOrder.orderId}`)
```
### Check Positions
```jsx theme={null}
const positions = await fetchAuthGet('/v1/perps/positions')
```
### Limit Order
```jsx theme={null}
const limitOrderReq = {
market: `${testMarket}-USD.P`,
type: 'limit',
side: 'buy',
price: '5280',
size: '0.02',
}
const placedLimitOrder = await fetchAuthPost('/v1/perps/orders', limitOrderReq)
```
Note: depending on market conditions, a limit order at this price may be rejected. Use the current market price as a reference.
> **Builder code:** Add `builderCode.code` and `builderCode.feeRateBpsFractional` to every order to earn incremental fees. Contact the Ondo Perps team to get your code activated. There is no centrally configured default fee rate for your builder code — omitting `feeRateBpsFractional` means that fill earns you no commission. Field name subject to change before v1.
### Position-Level Stop Order
Set a stop-loss or take-profit directly on an existing position, independent of any specific order (see [Take profit and stop loss](/take-profit-and-stop-loss)). `builderCode` (including `feeRateBpsFractional`) must be set explicitly on this request for the eventual triggered close to earn builder commission — it is **not** inherited from the `builderCode` on the order(s) that originally opened the position, and omitting it earns no commission.
```jsx theme={null}
const stopOrderReq = {
market: `${testMarket}-USD.P`,
positionDirection: 'long',
type: 'stopLoss',
triggerPrice: '5000',
builderCode: {
feeRateBpsFractional: 2.5,
code: 'yourCode'
}
}
const setStopOrder = await fetchAuthPost('/v1/perps/stop_order', stopOrderReq)
```
### Check builder statistics
The account that owns the builder code can retrieve the trading volume and builder fees attributed to it. Authenticate with the builder owner's JWT or an API key with read-only access.
`startTime` is required and uses Unix milliseconds. Omit `endTime` for live statistics:
```jsx theme={null}
const startTime = Date.now() - 30 * 24 * 60 * 60 * 1000
const liveStats = await fetchAuthGet(
`/v1/builder_code/stats?startTime=${startTime}`
)
```
Live requests round `startTime` down to a five-minute boundary and cache the resulting snapshot for one hour. The response returns the effective rounded `startTime` and the snapshot `endTime`. Repeated requests served from that cache retain both values.
Provide `endTime` for a fixed historical window:
```jsx theme={null}
const endTime = Date.now() - 2 * 60 * 1000
const historicalStats = await fetchAuthGet(
`/v1/builder_code/stats?startTime=${startTime}&endTime=${endTime}`
)
```
Historical windows use the exact inclusive bounds and are cached for 24 hours. An explicit `endTime` must be at least one minute in the past. Omit it for live statistics.
```json theme={null}
{
"startTime": 1789725600000,
"endTime": 1789729200000,
"totalVolume": "12500.123456789",
"totalBuilderFee": "12.500123456789",
"accounts": [
{
"accountId": "2564891920043613872",
"walletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
"totalVolume": "10000.123456789",
"builderFee": "10.000123456789"
},
{
"accountId": "2564891920043613873",
"walletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
"totalVolume": "2500",
"builderFee": "2.5"
}
]
}
```
Volume and fee fields are exact decimal strings. Each raw fill account ID has its own entry, so subaccounts remain separate. A subaccount reports the same primary wallet address as its main account. Account ordering is not guaranteed, and the response is not paginated. A window without fills returns `"0"` totals and `"accounts": []`.
### Stream builder fills
Statistics are cached and aggregated, so they lag live activity. To see individual fills as they happen, subscribe to the `builderFills` channel. It streams every fill attributed to your builder code, across all accounts trading with it, one fill per message.
Log in first — the channel requires authentication, and the logged-in account must own the builder code:
```jsx theme={null}
const wss = new WebSocket(`wss://${ondoBase}/ws`)
wss.addEventListener('open', () => {
wss.send(JSON.stringify({ op: 'login', args: { token: jwtToken } }))
})
wss.addEventListener('message', (event) => {
const obj = JSON.parse(event.data)
// Subscribe once the login is acknowledged
if (obj.type === 'loggedIn') {
wss.send(JSON.stringify({ op: 'subscribe', channel: 'builderFills' }))
}
if (obj.type === 'update' && obj.channel === 'builderFills') {
console.log('builder fill', obj.data)
}
})
```
Each update carries a single fill:
```json theme={null}
{
"type": "update",
"channel": "builderFills",
"data": {
"builderCodeFee": "0.2275",
"fillId": "70a37d8f972f2494837f9dba8364cbb4",
"market": "AAPL-USD.P",
"price": "227.50",
"size": "5.00",
"side": "buy",
"time": "2026-09-28T14:32:07.123456Z"
}
}
```
`builderCodeFee` is the commission you earned on that fill in USDC — the charged amount, not the rate. `side` is the side of the account that traded, not yours. There is no `markets` filter on this channel, and because builder codes apply to perps only, no spot fills arrive on it.
Subscribing before logging in returns `login required`. Subscribing with an account that does not own a builder code returns `channel unsupported`.
### Resynchronize after a disconnect
Messages are delivered only while you are subscribed, so anything that fills during a disconnect is missed. After reconnecting and resubscribing, fetch the most recent fills and de-duplicate against what you already have:
```jsx theme={null}
const recent = await fetchAuthGet('/v1/builder_code/fills')
// Newest first; skip fills already seen on the channel
const missed = recent.filter(fill => !seenFillIds.has(fill.fillId))
```
The response holds up to 200 fills, newest first, with the same shape as the channel payload. This is a short recovery window rather than a fill history — use the statistics endpoint for totals over an arbitrary period. The window is rebuilt when the exchange restarts, so it can hold fewer than 200 fills shortly afterwards. An owner with no recorded fills returns an empty array.
***
## Step 6 (Optional): Charting Data
Fetch chart data for TradingView or any charting library. This endpoint does not require authentication.
```jsx theme={null}
const fetchHistory = async (endpoint) => {
return fetch(`${ondoUrl}${endpoint}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
}).then(response => response.json())
}
const ts = Math.floor(Date.now() / 1000)
const history = await fetchHistory(
`/v1/perps/history?symbol=${testMarket}USD.P&resolution=15&to=${ts}&countback=2`
)
```
***
## Step 7 (Optional): WebSockets
Subscribe to real-time price updates via WebSocket.
```jsx theme={null}
const wss = new WebSocket(`wss://${ondoBase}/ws`)
wss.addEventListener('message', (event) => {
const data = event.data
const obj = JSON.parse(data)
switch (obj.type) {
case 'pong':
console.log('pong received')
break
case 'subscribed':
console.log(`subscribed: ${data}`)
break
case 'update':
console.log(`update: ${data}`)
break
default:
console.error(`unexpected message: ${data}`)
}
})
wss.addEventListener('open', () => {
console.log('connected to WS')
// Send pings every 1 second to keep the connection alive
let pingInterval = setInterval(() => {
const ping = {
op: 'ping',
id: window.crypto.randomUUID(),
}
wss.send(JSON.stringify(ping))
}, 1000)
// Subscribe to mark price updates
const markPriceSub = {
op: 'subscribe',
channel: 'markPricesPerps',
markets: [`${testMarket}-USD.P`],
}
wss.send(JSON.stringify(markPriceSub))
})
```
See the [WebSocket API docs](https://www.notion.so/Common-WebSocket-API-2ff278059c7f81c9ac10c8ffeb8b64dc?pvs=21) for all available channels and message formats.
***
## Sandbox vs Production
| | Sandbox | Production |
| - | - | - |
| REST API | `https://api.ondoperps-sandbox.xyz` | `https://api.ondoperps.xyz` |
| WebSocket | `wss://api.ondoperps-sandbox.xyz/ws` | `wss://api.ondoperps.xyz/ws` |
| Frontend | `https://app.ondoperps-sandbox.xyz` | `https://app.ondoperps.xyz` |
| Funds | Demo funds (no real money) | Real USDC deposits |
| Auth chain | Ethereum mainnet | Ethereum mainnet |
| Endpoints | Same | Same |
All endpoints work identically in both environments. Change the base URL and deposit flow, everything else stays the same.
***
## Full Example
Copy-paste the entire integration in one block. Mirrors Steps 2-7 above.
### Main Flow
```jsx theme={null}
// --- Step 2: Setup ---
const provider = window.ethereum
const ondoBase = 'api.ondoperps-sandbox.xyz'
const ondoUrl = `https://${ondoBase}`
const testMarket = 'XAU'
const parseResponse = (data) => {
if (!data.success) {
throw new Error(`Request failed: ${JSON.stringify(data)} (code: ${data.error_code})`)
}
return data.result
}
const fetchPost = async (endpoint, data) => {
return fetch(`${ondoUrl}${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
.then(response => response.json())
.then(data => parseResponse(data))
}
let jwtToken = ''
const fetchAuthPost = async (endpoint, data) => {
return fetch(`${ondoUrl}${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${jwtToken}`,
},
body: JSON.stringify(data),
})
.then(response => response.json())
.then(data => parseResponse(data))
}
const fetchAuthGet = async (endpoint) => {
return fetch(`${ondoUrl}${endpoint}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${jwtToken}`,
},
})
.then(response => response.json())
.then(data => parseResponse(data))
}
// --- Step 3: Authenticate (SIWE) ---
let chainId = await ethereum.request({ method: 'eth_chainId' })
if (chainId !== '0x1') {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x1' }],
})
}
const accounts = await provider.request({ method: 'eth_requestAccounts' })
const from = accounts[0]
const siweSign = async (siweMessage) => {
const bytes = new TextEncoder().encode(siweMessage)
const hex = Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('')
const msg = `0x${hex}`
return await provider.request({
method: 'personal_sign',
params: [msg, from],
})
}
const getChallengeData = {
walletAddress: from,
chainId: '1',
}
const challenge = await fetchPost('/v1/auth/erc-4361/login/get_challenge', getChallengeData)
const signedMessage = await siweSign(challenge.message)
const completeChallengeData = {
id: challenge.id,
signature: signedMessage,
}
const completeChallenge = await fetchPost('/v1/auth/erc-4361/login/complete_challenge', completeChallengeData)
jwtToken = completeChallenge.token
// Accept terms (first login only)
await fetchAuthPost('/v1/agreement', {
termsVersion: 1,
privacyVersion: 1,
})
// --- Step 4: Deposit ---
// Get your account ID
const accountData = await fetchAuthGet('/v1/account')
const accountID = accountData.accountID
// Request a deposit address
const provisionAddressReq = {
symbol: 'USDC',
deposit_destination: {
id: accountID,
wallet: 'margin',
},
network: 'ethereum', // also supports: solana, avalanche
}
const provisioned = await fetchAuthPost('/v1/provision_address', provisionAddressReq)
const depositAddress = provisioned.address
// Switch to the deposit chain (Sepolia for testnet, mainnet for production)
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0xAA36A7' }], // Sepolia testnet
})
// Send ERC-20 transfer: 50 USDC to the provisioned deposit address
// 0xa9059cbb = transfer(address,uint256) selector
// 0x02faf080 = 50,000,000 (50 USDC with 6 decimals)
const depositNoPrefix = depositAddress.replace(/^0x/i, '')
const sent_tx = await window.ethereum.request({
method: 'eth_sendTransaction',
params: [{
to: '0x94a9D9AC8a22534E3FaCa9F4e7F2E2cf85d5E4C8', // Sepolia USDC contract
from: from,
data: `0xa9059cbb000000000000000000000000${depositNoPrefix}0000000000000000000000000000000000000000000000000000000002faf080`,
}],
})
// Poll for transaction confirmation
for (;;) {
const tx = await window.ethereum.request({
method: 'eth_getTransactionByHash',
params: [sent_tx],
})
if (tx.blockHash && tx.blockHash !== '') {
break
}
// Wait 2 seconds between polls to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 2000))
}
console.log('Deposit confirmed on-chain')
// --- Step 5: Place Orders ---
const marketOrderReq = {
market: `${testMarket}-USD.P`,
type: 'market',
side: 'sell',
size: '0.01',
}
const placedMarketOrder = await fetchAuthPost('/v1/perps/orders', marketOrderReq)
const orderDetails = await fetchAuthGet(`/v1/perps/orders/${placedMarketOrder.orderId}`)
const positions = await fetchAuthGet('/v1/perps/positions')
// Uncomment to place a limit order:
// const limitOrderReq = {
// market: `${testMarket}-USD.P`,
// type: 'limit',
// side: 'buy',
// price: '5280',
// size: '0.02',
// }
// const placedLimitOrder = await fetchAuthPost('/v1/perps/orders', limitOrderReq)
// --- Step 6: Charting Data ---
const fetchHistory = async (endpoint) => {
return fetch(`${ondoUrl}${endpoint}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
}).then(response => response.json())
}
const ts = Math.floor(Date.now() / 1000)
const history = await fetchHistory(
`/v1/perps/history?symbol=${testMarket}USD.P&resolution=15&to=${ts}&countback=2`
)
```
### WebSockets
```jsx theme={null}
const ondoBase = 'api.ondoperps-sandbox.xyz'
const testMarket = 'XAU'
const wss = new WebSocket(`wss://${ondoBase}/ws`)
wss.addEventListener('message', (event) => {
const data = event.data
const obj = JSON.parse(data)
switch (obj.type) {
case 'pong':
console.log('pong received')
break
case 'subscribed':
console.log(`subscribed: ${data}`)
break
case 'update':
console.log(`update: ${data}`)
break
default:
console.error(`unexpected message: ${data}`)
}
})
wss.addEventListener('open', () => {
console.log('connected to WS')
let pingInterval = setInterval(() => {
const ping = {
op: 'ping',
id: window.crypto.randomUUID(),
}
wss.send(JSON.stringify(ping))
}, 1000)
const markPriceSub = {
op: 'subscribe',
channel: 'markPricesPerps',
markets: [`${testMarket}-USD.P`],
}
wss.send(JSON.stringify(markPriceSub))
})
```
***
## Changelog
| Version | Date | Changes |
| - | - | - |
| 1.0.0 | 2026-03-09 | Initial release. Sandbox-first guide with SIWE auth, deposit, order placement, charting, and WebSocket examples. |
| 1.0.1 | 2026-03-18 | Added Updated API Docs |
| 1.0.2 | 2026-03-20 | Added builder code in complete invite challenge path |
| 1.0.3 | 2026-06-01 | Moved builderCode from complete\_challenge to get\_challenge |
| 1.0.4 | 2026-08-19 | Documented builderCode.feeRateBpsFractional and builderCode on position-level stop orders |
| 1.0.5 | 2026-08-19 | Removed the JWT-embedded builderCode flow (get\_challenge); unused and slated for removal from the API. Set builderCode explicitly on each order/stop\_order request instead |
| 1.0.6 | 2026-09-21 | Added builder-code volume and fee statistics, including live and historical cache behavior. |
| 1.0.7 | 2026-10-01 | Added the `builderFills` WebSocket channel and `/v1/builder_code/fills` for streaming builder fills and resynchronizing after a disconnect. |
# Get Balance
Source: https://docs.ondoperps.xyz/api-reference/margin-account/get-balance
/api-reference/rest-spec.json get /v1/perps/balance
Get a summary of the balance for a margin account. For how positions and balances work, see [Positions and balances](/positions-balances).
# Get Liquidation History
Source: https://docs.ondoperps.xyz/api-reference/margin-account/get-liquidation-history
/api-reference/rest-spec.json get /v1/perps/liquidation_history
Returns a paginated history of liquidation events for the authenticated account. For how liquidations work, see [Liquidations](/def).
# Get Max Order Size
Source: https://docs.ondoperps.xyz/api-reference/margin-account/get-max-order-size
/api-reference/rest-spec.json get /v1/perps/max_order_size
Get a summary of the maximum order size that can be placed given the current state of the order book and available margin. For how size limits are determined, see [Position size limits](/maximum-position-sizes).
# Get Orders Summaries
Source: https://docs.ondoperps.xyz/api-reference/margin-account/get-orders-summaries
/api-reference/rest-spec.json get /v1/perps/orders_summaries
Returns a summary of resting orders, position, and market order margin requirements for each market.
# Get Candles
Source: https://docs.ondoperps.xyz/api-reference/market-data/get-candles
/api-reference/rest-spec.json get /v1/perps/candles
Returns OHLCV candlestick data for a perpetual futures market as an array of objects, each with the full field names `startTime`, `open`, `high`, `low`, `close`, and `volume` (values are strings). If you need the TradingView UDF format instead (parallel `o`/`h`/`l`/`c`/`v` arrays of numbers), use `GET /v1/perps/history` (Get Price History).
# Get Contracts
Source: https://docs.ondoperps.xyz/api-reference/market-data/get-contracts
/api-reference/rest-spec.json get /v1/perps/contracts
Gets a list of perpetual futures contracts and their current market data. For the list of supported markets, see [Markets](/markets).
# Get Open Interest
Source: https://docs.ondoperps.xyz/api-reference/market-data/get-open-interest
/api-reference/rest-spec.json get /v1/perps/open_interest
Get open interest across all perpetual futures markets.
# Get Order Book Depth
Source: https://docs.ondoperps.xyz/api-reference/market-data/get-order-book-depth
/api-reference/rest-spec.json get /v1/perps/depth
Returns the current order book depth snapshot for a market.
# Get Price History (TradingView)
Source: https://docs.ondoperps.xyz/api-reference/market-data/get-price-history-tradingview
/api-reference/rest-spec.json get /v1/perps/history
Returns historical bar data in TradingView UDF format, as required by the TradingView charting library: a single object with parallel arrays keyed `t`, `o`, `h`, `l`, `c`, and `v` (values are numbers), plus a status field `s`. For a standard array-of-objects representation with full field names (`open`, `high`, `low`, `close`, `volume`), use `GET /v1/perps/candles` (Get Candles).
# Get Symbol Info (TradingView)
Source: https://docs.ondoperps.xyz/api-reference/market-data/get-symbol-info-tradingview
/api-reference/rest-spec.json get /v1/perps/symbol_info
Returns symbol metadata in TradingView UDF format. See https://www.tradingview.com/rest-api-spec/#operation/getSymbolInfo.
# Get Trades
Source: https://docs.ondoperps.xyz/api-reference/market-data/get-trades
/api-reference/rest-spec.json get /v1/perps/trades
Returns a paginated list of public trades for a market.
# Get Volume
Source: https://docs.ondoperps.xyz/api-reference/market-data/get-volume
/api-reference/rest-spec.json get /v1/perps/volume
Get 24-hour trading volume across all perpetual futures markets.
# Get Markets
Source: https://docs.ondoperps.xyz/api-reference/markets/get-markets
/api-reference/rest-spec.json get /v1/markets
Returns available trading markets. For the list of supported markets, see [Markets](/markets).
If called with authentication, some fields are updated to reflect account-specific values. Read result.spot.tradingPairs for Spot market identifiers, increments, fees, and disabled status. Use authenticated configuration before trading because account access and fee arrangements can differ.
# Batch Cancel Orders
Source: https://docs.ondoperps.xyz/api-reference/orders/batch-cancel-orders
/api-reference/rest-spec.json delete /v1/perps/orders/batch
Cancels multiple orders in a single call. Returns results for each order. Provide a comma-separated list of order IDs via the `orderIDs` query parameter. Each entry can be an internal order ID or `client:{clientOrderID}`.
# Cancel All Orders
Source: https://docs.ondoperps.xyz/api-reference/orders/cancel-all-orders
/api-reference/rest-spec.json delete /v1/perps/orders
Cancels all orders for the authenticated user. Optionally restricted to a single market.
# Cancel Order by ID
Source: https://docs.ondoperps.xyz/api-reference/orders/cancel-order-by-id
/api-reference/rest-spec.json delete /v1/perps/orders/{orderID}
Cancels an individual order by order ID. The `orderID` path parameter can be:
- An internal order ID (e.g. `197ec08e001658690721be129e7fa595`).
- A client order ID prefixed with `client:` (e.g. `client:abc123`).
Returns a 400 with error code `order_already_cancelled`, `order_not_in_cancellable_state`, or `order_already_filled` if the order cannot be cancelled.
# Create Batched Orders
Source: https://docs.ondoperps.xyz/api-reference/orders/create-batched-orders
/api-reference/rest-spec.json post /v1/perps/orders/batch
Creates multiple orders in a single API call. Some orders may succeed while others fail. The response code is 2XX as long as there is no internal server error. Returns 400 with `error_code=batch_order_empty` if the `orders` array is empty. Returns 400 with `error_code=batch_order_too_many_orders` if too many orders are provided (max 20). For the available order types and their behavior, see [Order types](/order-types).
# Create Order
Source: https://docs.ondoperps.xyz/api-reference/orders/create-order
/api-reference/rest-spec.json post /v1/perps/orders
Creates a limit or market order. Change the "type" field to switch between order types. If `postOnly` is `true` and an order would match at placement, a 400 is returned with error `post_only_has_match`. For the available order types and their behavior, see [Order types](/order-types).
# Get Order by ID
Source: https://docs.ondoperps.xyz/api-reference/orders/get-order-by-id
/api-reference/rest-spec.json get /v1/perps/orders/{orderID}
Gets an individual order by order ID. The `orderID` path parameter can be:
- An internal order ID (e.g. `197ec08e001658690721be129e7fa595`).
- A client order ID prefixed with `client:` (e.g. `client:abc123`).
# Get Orders
Source: https://docs.ondoperps.xyz/api-reference/orders/get-orders
/api-reference/rest-spec.json get /v1/perps/orders
Returns a paginated list of orders matching the given parameters.
# Get Orders CSV
Source: https://docs.ondoperps.xyz/api-reference/orders/get-orders-csv
/api-reference/rest-spec.json get /v1/perps/orders/csv
Export filled orders as CSV, sorted reverse chronologically.
# Get Portfolio Summary
Source: https://docs.ondoperps.xyz/api-reference/portfolio/get-portfolio-summary
/api-reference/rest-spec.json get /v1/portfolio/summary
Returns current portfolio metrics for the authenticated account: margin balance, net invested, PnL, volume metrics, and Sharpe ratios (when enabled and sufficient data).
# Get Portfolio Summary Graph
Source: https://docs.ondoperps.xyz/api-reference/portfolio/get-portfolio-summary-graph
/api-reference/rest-spec.json get /v1/portfolio/summary/graph
Returns time-series portfolio snapshots for the authenticated account. Optional query parameter `range` controls the bucket size: 7d (default), 24h, 30d, or all.
# Get Leverage
Source: https://docs.ondoperps.xyz/api-reference/positions/get-leverage
/api-reference/rest-spec.json get /v1/perps/leverage
Returns the current leverage setting(s) for the authenticated account. If `market` is provided, returns leverage for that market only; otherwise returns leverage for all markets. For how leverage and margin work, see [Leverage](/leverage).
# Get Positions
Source: https://docs.ondoperps.xyz/api-reference/positions/get-positions
/api-reference/rest-spec.json get /v1/perps/positions
Returns all open positions for the authenticated account. For how positions and balances work, see [Positions and balances](/positions-balances).
# Set Leverage
Source: https://docs.ondoperps.xyz/api-reference/positions/set-leverage
/api-reference/rest-spec.json post /v1/perps/leverage
Sets the leverage for a market. Leverage must be a positive integer and cannot exceed the market's maximum leverage. Reducing leverage while insufficient margin is available will return 400 with `error_code=insufficient_margin`. For how leverage and margin work, see [Leverage](/leverage).
# Subscribe: Builder Code Fills
Source: https://docs.ondoperps.xyz/api-reference/private-channels/subscribe:-builder-code-fills
/api-reference/ws-spec.json post /ws/builderFills
Subscribe to the `builderFills` channel. Requires authentication (login first), and the logged-in account must own a builder code.
Streams every fill attributed to that builder code, across all accounts trading with it, one fill per message. There is no `markets` filter. Builder codes apply to perps only, so no spot fills arrive on this channel.
Subscribing without a builder code on the account returns `channel unsupported`; subscribing before logging in returns `login required`.
Messages are delivered only while subscribed, so a disconnect drops anything that fills in the meantime. After reconnecting, call `GET /v1/builder_code/fills` to recover the most recent fills and de-duplicate by `fillId`.
# Subscribe: Deposits
Source: https://docs.ondoperps.xyz/api-reference/private-channels/subscribe:-deposits
/api-reference/ws-spec.json post /ws/deposits
Subscribe to the `deposits` channel. Requires authentication (login first).
# Subscribe: Perps Balance
Source: https://docs.ondoperps.xyz/api-reference/private-channels/subscribe:-perps-balance
/api-reference/ws-spec.json post /ws/balancePerps
Subscribe to the `balancePerps` channel. Requires authentication (login first).
# Subscribe: Perps Dead Man's Switch
Source: https://docs.ondoperps.xyz/api-reference/private-channels/subscribe:-perps-dead-mans-switch
/api-reference/ws-spec.json post /ws/cancelAllOrdersAfterPerps
Subscribe to the `cancelAllOrdersAfterPerps` channel. Requires authentication (login first).
**Required**: `timeout_seconds` (the dead man's switch duration in seconds).
# Subscribe: Perps Fills
Source: https://docs.ondoperps.xyz/api-reference/private-channels/subscribe:-perps-fills
/api-reference/ws-spec.json post /ws/fillsPerps
Subscribe to the `fillsPerps` channel. Requires authentication (login first).
Optional `markets` to filter.
# Subscribe: Perps Funding Payments
Source: https://docs.ondoperps.xyz/api-reference/private-channels/subscribe:-perps-funding-payments
/api-reference/ws-spec.json post /ws/fundingPaymentsPerps
Subscribe to the `fundingPaymentsPerps` channel. Requires authentication (login first).
# Subscribe: Perps Liquidation
Source: https://docs.ondoperps.xyz/api-reference/private-channels/subscribe:-perps-liquidation
/api-reference/ws-spec.json post /ws/liquidationPerps
Subscribe to the `liquidationPerps` channel. Requires authentication (login first).
# Subscribe: Perps Liquidation Announcements
Source: https://docs.ondoperps.xyz/api-reference/private-channels/subscribe:-perps-liquidation-announcements
/api-reference/ws-spec.json post /ws/liquidationAnnouncementsPerps
Subscribe to the `liquidationAnnouncementsPerps` channel. Requires authentication (login first).
# Subscribe: Perps Margin Transfers
Source: https://docs.ondoperps.xyz/api-reference/private-channels/subscribe:-perps-margin-transfers
/api-reference/ws-spec.json post /ws/marginTransfersPerps
Subscribe to the `marginTransfersPerps` channel. Requires authentication (login first).
# Subscribe: Perps Order Summaries
Source: https://docs.ondoperps.xyz/api-reference/private-channels/subscribe:-perps-order-summaries
/api-reference/ws-spec.json post /ws/ordersSummariesPerps
Subscribe to the `ordersSummariesPerps` channel. Requires authentication (login first).
# Subscribe: Perps Orders
Source: https://docs.ondoperps.xyz/api-reference/private-channels/subscribe:-perps-orders
/api-reference/ws-spec.json post /ws/ordersPerps
Subscribe to the `ordersPerps` channel. Requires authentication (login first).
Optional `markets` to filter.
# Subscribe: Perps Positions
Source: https://docs.ondoperps.xyz/api-reference/private-channels/subscribe:-perps-positions
/api-reference/ws-spec.json post /ws/positionsPerps
Subscribe to the `positionsPerps` channel. Requires authentication (login first).
# Subscribe: Withdrawals
Source: https://docs.ondoperps.xyz/api-reference/private-channels/subscribe:-withdrawals
/api-reference/ws-spec.json post /ws/withdrawals
Subscribe to the `withdrawals` channel. Requires authentication (login first).
# Subscribe: Perps Depth Book
Source: https://docs.ondoperps.xyz/api-reference/public-channels/subscribe:-perps-depth-book
/api-reference/ws-spec.json post /ws/depthBooksPerps
Subscribe to the `depthBooksPerps` channel.
Optional `markets`, `depthLevels` (price grouping), and `limit` (max levels).
# Subscribe: Perps Funding Rates
Source: https://docs.ondoperps.xyz/api-reference/public-channels/subscribe:-perps-funding-rates
/api-reference/ws-spec.json post /ws/fundingRatesPerps
Subscribe to the `fundingRatesPerps` channel.
Optional `markets` to filter.
# Subscribe: Perps Kline / Candlesticks
Source: https://docs.ondoperps.xyz/api-reference/public-channels/subscribe:-perps-kline-candlesticks
/api-reference/ws-spec.json post /ws/kLinePerps
Subscribe to the `kLinePerps` channel.
**Required**: exactly one `markets` entry and a `resolution`.
Valid resolutions: `1`, `5`, `15`, `1H`, `4H`, `1D`, `1W`.
# Subscribe: Perps Mark Prices
Source: https://docs.ondoperps.xyz/api-reference/public-channels/subscribe:-perps-mark-prices
/api-reference/ws-spec.json post /ws/markPricesPerps
Subscribe to the `markPricesPerps` channel.
Optional `markets` to filter.
# Subscribe: Perps Top of Book
Source: https://docs.ondoperps.xyz/api-reference/public-channels/subscribe:-perps-top-of-book
/api-reference/ws-spec.json post /ws/topOfBooksPerps
Subscribe to the `topOfBooksPerps` channel.
Optional `markets` to filter; omit for all markets.
# Subscribe: Perps Trades
Source: https://docs.ondoperps.xyz/api-reference/public-channels/subscribe:-perps-trades
/api-reference/ws-spec.json post /ws/tradesPerps
Subscribe to the `tradesPerps` channel.
Optional `markets` and `numPastTrades` (number of historical trades on subscribe).
# Sandbox Deposit
Source: https://docs.ondoperps.xyz/api-reference/sandbox/sandbox-deposit
/api-reference/rest-spec.json post /v1/sandbox_deposit
Credits the account with funds in sandbox environment only.
# Sandbox Withdrawal
Source: https://docs.ondoperps.xyz/api-reference/sandbox/sandbox-withdrawal
/api-reference/rest-spec.json post /v1/sandbox_withdrawal
Debits the account in sandbox environment only.
# Spot API guide
Source: https://docs.ondoperps.xyz/api-reference/spot-api-guide
Build a Spot client with REST orders, transfers, market data, and WebSocket reconciliation.
Build a client that discovers Spot markets, funds its Spot wallet, places and cancels orders, and reconciles executions over REST and WebSocket.
**Use WebSocket for ongoing data updates.** Keep a persistent connection and subscribe to market data, orders, fills, and balances where available. Maintain local state from those updates instead of repeatedly polling REST.
Use REST to place and cancel orders, transfer funds, load initial state, fetch data without a WebSocket channel, and reconcile after reconnects or uncertain results.
Spot trading uses a central limit order book. Orders spend the available balance in the **spot** wallet. Standard Spot trading is fully funded: a buy spends the quote asset, and a sell spends an existing holding of the base asset.
Examples use `SPY-USDC` with illustrative prices, sizes, and fees. Select an enabled market and its increments from your authenticated market configuration before submitting orders.
## 1. Connect and authenticate
Use credentials issued for the environment and account you intend to trade.
| Environment | REST base URL | WebSocket URL |
| :- | :- | :- |
| Production | `https://api.ondoperps.xyz` | `wss://api.ondoperps.xyz/ws` |
| Sandbox | `https://api.ondoperps-sandbox.xyz` | `wss://api.ondoperps-sandbox.xyz/ws` |
Market availability and account access can differ between environments. An endpoint being reachable does not establish that a market is enabled for your account.
Create your API key through the authenticated account interface. API keys cannot create or manage other API keys. Keep the full key ID and secret, including their prefixes. Store the secret on the server and exclude it from logs.
| Permission | Use |
| :- | :- |
| Read access | Any valid API key can use endpoints requiring `view`. |
| `trade` | Place and cancel orders. |
| `transfer` | Move funds between supported wallets. |
A key identifies one account. To trade a subaccount, use a key created for that subaccount; do not add a subaccount override header to an API-key request. An IP allowlist, if configured on the key, must include your client's egress IP.
### REST signature
Send these headers on authenticated requests:
| Header | Value |
| :- | :- |
| `ONDO-KEY-ID` | Full API key ID. |
| `ONDO-TIMESTAMP` | Unix timestamp in **milliseconds**, formatted as a decimal string. |
| `ONDO-SIGN` | Hex-encoded HMAC-SHA256 signature. |
| `Content-Type` | `application/json` when sending JSON. |
Construct the signed bytes without separators:
```text theme={null}
message = timestamp + HTTP_METHOD + path_with_query + body
signature = hex(HMAC_SHA256(secret_as_UTF8_bytes, message_as_bytes))
```
Use the uppercase HTTP method. Include the exact encoded path and query string, including `?`, parameter order, and percent encoding. Exclude the scheme and host. Sign the exact body bytes you send; a bodyless request contributes zero bytes, not `null` or `{}`. Do not hex-decode or Base64-decode the secret.
The timestamp may be at most **10 seconds behind** or **1 second ahead** of server time. Synchronize your clock, and generate a fresh timestamp and signature for each request. A signature does not make a write idempotent.
### Python client
This example uses the Python standard library. Save it as `spot_client.py`. Set `ONDO_API_BASE`, `ONDO_KEY_ID`, and `ONDO_API_SECRET` in your environment. Running the file only reads market configuration and balances.
```python theme={null}
import hashlib
import hmac
import json
import os
import time
from urllib.error import HTTPError
from urllib.parse import urlencode, urlsplit
from urllib.request import HTTPRedirectHandler, Request, build_opener
class APIError(Exception):
def __init__(self, status, payload):
self.status = status
self.payload = payload
super().__init__(f"HTTP {status}: {payload}")
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
BASE = os.environ["ONDO_API_BASE"].rstrip("/")
KEY = os.environ["ONDO_KEY_ID"]
SECRET = os.environ["ONDO_API_SECRET"].encode("utf-8")
if urlsplit(BASE).scheme != "https":
raise ValueError("ONDO_API_BASE must use HTTPS")
http = build_opener(NoRedirect())
def sign(timestamp, method, path="", body=b""):
message = (timestamp + method + path).encode("utf-8") + body
return hmac.new(SECRET, message, hashlib.sha256).hexdigest()
def api(method, path, query=None, payload=None):
method = method.upper()
if query:
path += "?" + urlencode(query)
body = b"" if payload is None else json.dumps(
payload, separators=(",", ":"), ensure_ascii=False
).encode("utf-8")
timestamp = str(time.time_ns() // 1_000_000)
request = Request(
BASE + path,
data=None if payload is None else body,
method=method,
headers={
"ONDO-KEY-ID": KEY,
"ONDO-TIMESTAMP": timestamp,
"ONDO-SIGN": sign(timestamp, method, path, body),
"Content-Type": "application/json",
},
)
try:
with http.open(request, timeout=15) as response:
result = json.load(response)
status = response.status
except HTTPError as exc:
raw = exc.read().decode("utf-8", errors="replace")
try:
payload = json.loads(raw)
except ValueError:
payload = {"error": raw}
raise APIError(exc.code, payload) from exc
if result.get("success") is not True:
raise APIError(status, result)
return result
def websocket_login():
timestamp = str(time.time_ns() // 1_000_000)
return {
"op": "login",
"args": {
"key": KEY,
"time": timestamp,
"sign": sign(timestamp, "ondo_perps_ws_login"),
},
}
if __name__ == "__main__":
markets = api("GET", "/v1/markets")["result"]
print(json.dumps(markets["spot"], indent=2))
print(json.dumps(api("GET", "/v1/spot/balances"), indent=2))
```
Transport errors, timeouts, and non-JSON responses need handling in your application. The helper does not automatically retry writes or follow redirects. Before retrying an order or transfer whose outcome is unknown, reconcile it as described below.
## 2. Discover markets and units
`GET /v1/markets` supports public and authenticated requests. **Use an authenticated request for trading**: the response reflects account-specific market access and maker/taker fees.
Read `result.spot.tradingPairs`. A market entry has this shape; these values are illustrative:
```json theme={null}
{
"market": "SPY-USDC",
"pair": {"base": "SPY", "quote": "USDC"},
"baseIncrement": "0.01",
"quoteIncrement": "0.01",
"makerFee": "0",
"takerFee": "0"
}
```
| Field | Meaning |
| :- | :- |
| `market` | Exact identifier used in orders, filters, and subscriptions. Preserve its spelling and case. |
| `pair.base`, `pair.quote` | Asset bought/sold, and asset used to price it. |
| `baseIncrement` | Order size must be a positive multiple of this increment. |
| `quoteIncrement` | Limit price must be a positive multiple of this increment. |
| `makerFee`, `takerFee` | Decimal fractions, not percentages or basis points. `0.0001` means 1 basis point. |
| `disabled` | When `true`, the market is disabled. This field can be omitted when false. |
| `tags` | Optional market categories. |
Use decimal arithmetic for prices, sizes, balances, and fees. Send them as JSON strings. Do not use binary floating point to align an order to an increment. Preserve IDs as strings, even when they contain only digits. Accept additional response fields for forward compatibility.
For GM assets, inspect the corresponding entry in `result.tokenConfig`: `id`, `decimals`, `ledgerUnit`, `custodiedAs`, and `sharesMultiplier`. When `ledgerUnit` is `shares`, order sizes, fills, balances, and internal transfers use underlying share-equivalent units. `custodiedAs` names the external wrapper token.
For example, a `SPY-USDC` order with `size: "2"` trades two SPY-equivalent units at a price in USDC per unit. It does not request two SPYon wrapper tokens. If the applied multiplier is 1.02 shares per token, 10 wrapper tokens correspond to 10.2 share units before applicable precision handling. Internal wallet transfers do not apply the multiplier again. Deposit and withdrawal conversion occurs at the custody boundary; use the credited or executed amounts for reconciliation rather than recomputing past events using a later multiplier.
Refresh configuration on reconnect, before starting a trading session, and after a market-disabled or increment-related rejection. A listed asset, a tradable pair, a supported funding network, and eligibility as Perps collateral are distinct capabilities.
## 3. Fund the Spot wallet
The wallet identifiers are `spot` for Spot trading, `margin` for Perps collateral, and `main` for the main wallet. Funds in another wallet do not automatically fund a Spot order.
### Read balances
`GET /v1/spot/balances` requires authentication and returns an array in `result`:
```json theme={null}
{
"success": true,
"result": [
{"coin": "USDC", "total": "1000", "reserved": "600", "free": "400", "usdValue": "1000"},
{"coin": "SPY", "total": "2", "reserved": "0", "free": "2", "usdValue": "1200"}
]
}
```
`total` is the held quantity. `reserved` is the quantity held by open Spot orders. `free = total - reserved` is available to fund additional orders, subject to account restrictions. `usdValue` is an optional estimate; it is not an executable price and can be zero when valuation is unavailable. Key rows by `coin`, not array position. Zero-balance rows can be returned.
For a limit buy, the funding requirement is `price × size` in the quote asset. For a sell, it is `size` in the base asset. Open-order reservations prevent those funds from being reused. A completed cancellation releases the reservation for the unfilled portion. Withdrawal availability is subject to its own checks and is not established by this endpoint.
### Transfer funds
`POST /v1/spot/transfers` requires `transfer`. It accepts `main` ↔ `spot` and `margin` ↔ `spot` transfers on the same account. Margin transfers also require asset eligibility and sufficient withdrawable margin.
The following example moves 1,000 USDC from Perps collateral to Spot. Execute it only after selecting the amount to move:
```python theme={null}
from spot_client import api
transfer = api("POST", "/v1/spot/transfers", payload={
"from": {"wallet": "margin"},
"to": {"wallet": "spot"},
"amount": "1000",
"symbol": "USDC",
})
print(transfer)
```
The optional `id` inside `from` and `to` defaults to the authenticated account ID. If supplied, both must identify that same account. Amount must be positive and conform to the asset's `decimals`. The shared `GET /v1/account` endpoint returns the authenticated `accountID` when you need it.
The response `result` is a transfer record containing `id`, `from`, `to`, `amount`, `symbol`, `time`, and a numeric `type`. For a margin/spot move, the returned record represents the leg crediting your destination wallet; its `from.wallet` can therefore be `main`, even when the request starts from `margin` or `spot`. Preserve the response and read the destination balance to reconcile the move.
`GET /v1/spot/transfers` returns paginated Spot movement history. Supported filters are `startTime`, `endTime`, `cursor`, and `limit`. It includes Spot transfers, subaccount transfers involving Spot, and share adjustments; it excludes trading-fee transfers. Use fills to account for trading fees.
Transfer creation has no client-supplied idempotency key. After a timeout, inspect balances and transfer history before resubmitting. If the move cannot be reconciled, retain the account, asset, amount, time, and any returned transfer ID for support.
## 4. Read market data
| Method and path | Authentication | Parameters and result |
| :- | :- | :- |
| `GET /v1/spot/ticker` | Public | All Spot tickers. Optional `sparkline=true`. No market filter; select the row by `market`. |
| `GET /v1/spot/depth` | Public | Required `market`; optional integer `depth`, default 10, range 1–100. One book snapshot. NOTE: do NOT spam this endpoint for orderbook updates. use the websocket instead. |
| `GET /v1/spot/trades` | Public | Required `market`; optional `cursor`, `limit`. Paginated public trades. |
| `GET /v1/spot/candles` | Authenticated | Required `market`, `resolution`, `to`, and either `from` or `countback`. Array of candles. |
Book request:
```text theme={null}
GET /v1/spot/depth?market=SPY-USDC&depth=10
```
Illustrative response:
```json theme={null}
{
"success": true,
"result": {
"market": "SPY-USDC",
"time": "2026-09-14T12:00:00Z",
"bids": [["599.99", "10"]],
"asks": [["600.01", "8"]]
}
}
```
Bids are buys and asks are sells. Each level is a two-element array `[price, size]` of decimal strings. Level size is aggregate base quantity. Bids run from highest to lowest price; asks run from lowest to highest. An empty side can be represented by an empty array or `null`; treat either as no displayed liquidity. Snapshot quantities are absolute values.
Ticker rows include `market`, `disabled`, `baseCurrency`, `quoteCurrency`, `lastPrice`, `bid`, `ask`, `baseVolume`, `quoteVolume`, `high`, `low`, and `priceChangePercent`. Volume and high/low describe the trailing 24-hour window. A ticker or last trade does not guarantee a currently available execution price.
Public trades contain `id`, `market`, `price`, `size`, `cost`, `aggressor_side`, and `time`. `aggressor_side` is the taker's side. These are market-wide trades; they do not identify your account's fills.
For REST candles, `from` and `to` are Unix **seconds**. Supported resolutions are `1`, `5`, `15`, `60`, `240`, `1D`, and `1W` (`D` and `W` are accepted aliases). `countback` requests a positive number of bars ending at `to`. Align time ranges to bar boundaries; the server can round the requested boundaries. `to` is inclusive. Each candle has `startTime`, `open`, `high`, `low`, `close`, and base-asset `volume`. Prices and volume are decimal strings; `startTime` is an RFC 3339 timestamp.
### Minimum size and price protection
Order size must be a positive multiple of `baseIncrement`. The smallest permitted quantity is one `baseIncrement`; there is no separate minimum-notional requirement in the standard Spot order validator. For example, a market with `baseIncrement: "0.01"` accepts quantities such as `0.01`, `0.02`, and `1.23`, but not `0.001` or `0.015`.
A limit price must be positive, less than 10,000,000, and a multiple of `quoteIncrement`. Market orders use base-denominated `size`; omit `price` and `timeInForce`. Positive `quoteSize` is not supported. Meeting the increments does not guarantee acceptance: available funds, market access, liquidity, and [price protection](/mark-price-protection) are checked separately.
Spot price protection uses the underlying oracle price. The configured percentage can vary; 10% is the fallback. GTC orders use directional price bounds, while market and IOC orders are checked using simulated execution. The rules below describe the boundaries and IOC partial-fill behavior.
### Price protection rules
#### Protection percentage
The protection percentage can vary by market and market type. The default fallback is 10%; it is not a fixed promise for every market. A missing or zero reference price prevents an order from passing price validation.
#### GTC limit orders
The check is directional: it prevents a buy priced too high or a sell priced too low. With reference price `R` and configured protection fraction `f`, the ordinary bounds are:
* Buy: `limit price <= R × (1 + f)`.
* Sell: `limit price >= R × (1 - f)`.
Equality at the boundary passes this check. A passive buy below the reference or a passive sell above it is not rejected merely for being more than `f` away, though other validation rules still apply.
For example, if a Spot asset's oracle price is 100 USDC and its configured fraction is 10%, a GTC buy limit above 110 or a sell limit below 90 is rejected by this check. Perpetual price-discovery bounds, when active, can tighten the corresponding boundary; those bounds do not apply to Spot.
#### Market and IOC limit orders
Market orders and IOC limit orders are checked by simulating execution against available liquidity. A market order is rejected if the simulation cannot fill the requested quantity within the system's protective boundary.
For an IOC limit order, execution is bounded by the tighter of your limit and the system boundary. When your limit is at least as restrictive as the system boundary, a partial fill is allowed and the remainder is canceled. A limit outside the system boundary is not automatically rejected solely for that limit; it must be fully executable within the system boundary during validation.
Validation is not a fill guarantee. Review the returned order status and fills. Use an IOC limit order when you need to specify your own execution-price bound. See [Order types](/order-types).
## 5. Place an order
`POST /v1/spot/orders` requires `trade`. Requests and responses use `Content-Type: application/json`.
| Field | Requirement |
| :- | :- |
| `market` | Required enabled market identifier. |
| `side` | Required: `buy` or `sell`. |
| `type` | Use `limit` or `market`. |
| `size` | Required positive base quantity, aligned to `baseIncrement`. |
| `price` | Required positive limit price, aligned to `quoteIncrement`. Omit for a market order. |
| `timeInForce` | Limit orders only: `GTC` or `IOC`, uppercase. Defaults to `GTC`. Omit for a market order. |
| `postOnly` | Optional boolean, default false. Use with a `GTC` limit order. |
| `clientOrderId` | Optional but recommended. At most 64 ASCII characters: letters, digits, underscore, or hyphen. Use a unique value for every new order. |
`quoteSize` orders are unsupported and a positive `quoteSize` is rejected. Use base-denominated `size` for both buys and sells. Spot does not provide borrowing, a leverage field, or a reduce-only position workflow. Do not send `builderCode` or `rpi`, which are Perps-only. Do not send Perps stop-loss/take-profit fields or submit `stopMarket` or `takeProfitMarket` directly.
### Limit order
This submits a buy for up to two SPY-equivalent units at 600 USDC per unit or better:
```python theme={null}
from spot_client import api
from uuid import uuid4
client_id = "spot-" + uuid4().hex
order = api("POST", "/v1/spot/orders", payload={
"market": "SPY-USDC",
"side": "buy",
"type": "limit",
"price": "600",
"size": "2",
"timeInForce": "GTC",
"clientOrderId": client_id,
})["result"]
print(order)
```
A GTC order may execute immediately. Its unfilled remainder can rest on the book. Set `postOnly: true` to require that it rest; a post-only order that would immediately match is rejected. Post-only and IOC cannot be combined.
### Immediate-or-cancel limit order
Use `timeInForce: "IOC"` to execute available quantity at the limit price or better and cancel any remainder. A partial fill is possible; IOC is not fill-or-kill.
```json theme={null}
{
"market": "SPY-USDC",
"side": "sell",
"type": "limit",
"price": "600",
"size": "1",
"timeInForce": "IOC",
"clientOrderId": "spot-ioc-001"
}
```
### Market order
A market order consumes available liquidity. It may fill at multiple prices or partially fill. Use an IOC limit order when your instruction requires an explicit execution-price bound.
```json theme={null}
{
"market": "SPY-USDC",
"side": "buy",
"type": "market",
"size": "1",
"clientOrderId": "spot-market-001"
}
```
Omit `price` and `timeInForce`. A returned market-order `price` of `"0"` is an order-type marker, not the execution price. Read its fills or divide `filledCost` by `filledSize` when `filledSize` is positive. Market orders can be disabled independently; handle `market_orders_disabled` without assuming the order was placed.
### Delayed placement
Some accounts or order flows may require the delayed placement paths:
* `POST /v1/spot/orders/delayed`
* `POST /v1/spot/orders/batch/delayed`
They accept the same bodies as their ordinary counterparts. If the server rejects placement with a message directing you to a delayed path, sign a new request using that exact path and a fresh timestamp. Do not reuse a signature from the original URL. Allow time for the server's delay; an ambiguous timeout still requires reconciliation.
## 6. Track orders and fills
Order placement returns `success: true` and an order object in `result`. It can already be partially filled, fully filled, or canceled when you receive the response. An acknowledgement does not guarantee that an order remains open.
Illustrative resting-order response:
```json theme={null}
{
"success": true,
"result": {
"orderId": "order-example-001",
"clientOrderId": "spot-limit-001",
"market": "SPY-USDC",
"side": "buy",
"type": "limit",
"price": "600",
"size": "2",
"filledSize": "0",
"lastFillSize": "0",
"filledCost": "0",
"fee": "0",
"status": "open",
"timeInForce": "GTC",
"createdAt": "2026-09-14T12:00:00Z"
}
}
```
| Field | Interpretation |
| :- | :- |
| `orderId` | Exchange-assigned order ID. |
| `size` | Requested base quantity. |
| `filledSize` | Cumulative executed base quantity, before a buy fee is deducted. |
| `lastFillSize` | Quantity of the most recent fill. |
| `filledCost` | Cumulative executed quote amount. |
| `fee` | Cumulative fee in the asset received: base for buys, quote for sells. |
| `status` | Usually `open`, `fullyfilled`, or `canceled` for Spot. |
| `createdAt` | Creation timestamp. |
| `filledAt`, `canceledAt`, `cancelReason` | Optional completion/cancellation metadata. |
An `open` order can have nonzero `filledSize`; there is no separate partially-filled status. A `canceled` order can also have fills. In JSON responses the fully filled status is **`fullyfilled`**. The order-history query filter instead uses **`status=fullyFilled`**.
### Get one order
```text theme={null}
GET /v1/spot/orders/{orderId}
GET /v1/spot/orders/client:{clientOrderId}
```
Both require authentication. `client:` is a path lookup prefix; do not include it in the submitted `clientOrderId`.
After a timeout, query the original `clientOrderId` before submitting another order. Reusing an accepted ID returns `clientOrderID_collision`; it does not return the original response. A not-found result immediately after a timeout is not proof that the original request cannot still complete. Keep the original ID and reconcile the order and fills before deciding to submit a replacement with a new ID.
### List orders
`GET /v1/spot/orders` requires authentication. Supported parameters:
| Parameter | Meaning |
| :- | :- |
| `market` | Filter by exact market. Omit for all Spot markets. |
| `status` | For standard Spot orders: `open`, `fullyFilled`, or `canceled`. |
| `orderType` | `limit`, `market`, or a comma-separated list. |
| `hasFill` | Boolean; `true` selects orders with executions. |
| `activeOnly` | Boolean; selects active orders. For ordinary Spot orders, use `status=open` for the open-order set. |
| `startTime`, `endTime` | Creation-time bounds in Unix milliseconds. |
| `cursor`, `limit` | Pagination controls. |
```text theme={null}
GET /v1/spot/orders?market=SPY-USDC&status=open&limit=100
```
### Read fills
These authenticated endpoints return your executions:
```text theme={null}
GET /v1/spot/fills?market=SPY-USDC&limit=100
GET /v1/spot/orders/{orderId}/fills
GET /v1/spot/orders/client:{clientOrderId}/fills
```
The list endpoint supports `market`, `startTime`, `endTime`, `cursor`, and `limit`. Time filters are Unix milliseconds. The by-order endpoint returns an array without pagination.
Illustrative buy fill, as an item inside `result`:
```json theme={null}
{
"id": "fill-example-001",
"orderId": "order-example-001",
"clientOrderId": "spot-limit-001",
"market": "SPY-USDC",
"price": "600",
"size": "1",
"side": "buy",
"filledCost": "600",
"fee": "0",
"time": "2026-09-14T12:00:01Z",
"isMaker": false
}
```
Deduplicate private executions by fill `id`. `isMaker` identifies whether your order supplied resting liquidity for that execution. One order can have multiple fills and may incur both maker and taker fees across its lifetime.
## 7. Calculate fees and reconcile balances
**Launch promotion: Spot maker and taker trading fees are 0% for the first 30 days after Spot launch.** This window starts at launch, not at your first trade. The fee calculations below explain how nonzero rates are applied; use the rate returned for your account. See [Fees](/fees).
Read your maker/taker rates from authenticated market configuration. Rates are captured when an order is accepted; a later configuration change does not reprice a resting order's captured rates. Fees are rounded per fill to the fee asset's precision. Use returned fee amounts for accounting.
| Side | Asset spent | Asset received | Fee asset | Net received |
| :- | :- | :- | :- | :- |
| Buy | Quote | Base | Base | `size - fee` |
| Sell | Base | Quote | Quote | `filledCost - fee` |
At an illustrative taker rate of `0.0002`, a buy of 1 SPY unit at 600 USDC spends 600 USDC and receives 0.9998 SPY units. A sell of 1 SPY unit at the same price receives 599.88 USDC after a 0.12 USDC fee, subject to fee rounding.
The buy fill's `size` remains `"1"`; it is not the net balance credit. Do not assume `fee` is always denominated in USDC, and do not subtract it twice when comparing against reported balances.
To reconcile an interval, account for fills and fees, wallet transfers, and share adjustments. Order events alone cannot explain every balance change.
## 8. Cancel orders and submit batches
All cancellation endpoints require `trade`.
| Action | Request |
| :- | :- |
| Cancel one order | `DELETE /v1/spot/orders/{orderId}` |
| Cancel using your ID | `DELETE /v1/spot/orders/client:{clientOrderId}` |
| Cancel all orders in a market | `DELETE /v1/spot/orders?market=SPY-USDC` |
| Cancel all Spot orders | `DELETE /v1/spot/orders` |
| Cancel up to 20 orders | `DELETE /v1/spot/orders/batch?orderIDs=id1,id2` |
The batch parameter is **`orderIDs`**, with capital `IDs`, in the query string. It accepts exchange IDs or `client:`-prefixed client IDs. URL-encode the query and sign the encoded form. Cancel-all is scoped to Spot; it does not cancel Perps orders.
A cancel cannot undo a fill. After an ambiguous cancellation result, reread the order and fills. An already-filled or already-canceled response requires reconciliation rather than repeated blind cancellation.
### Batch placement
`POST /v1/spot/orders/batch` accepts **1–20 orders**:
```json theme={null}
{
"orders": [
{"market": "SPY-USDC", "side": "buy", "type": "limit", "price": "599", "size": "1", "timeInForce": "GTC", "postOnly": true, "clientOrderId": "batch-bid-001"},
{"market": "SPY-USDC", "side": "sell", "type": "limit", "price": "601", "size": "1", "timeInForce": "GTC", "postOnly": true, "clientOrderId": "batch-ask-001"}
]
}
```
The batch is **not all-or-nothing**. A valid batch request can return HTTP success while individual orders fail. Always inspect both `result.addedOrders` and `result.failedOrders`. Each failure contains `order`, `error`, and `errorCode`.
Batch cancellation similarly returns `result.successfulCancels` and `result.failedCancels`. Each cancellation failure contains `orderId`, `error`, and `errorCode`. Item-level `errorCode` uses camel case; the outer error envelope uses `error_code`.
A batch is not an atomic cancel-and-replace operation. Reconcile each order by ID, and resubmit only items whose outcomes are known.
## 9. Pagination and REST errors
Paginated endpoints put the result array and cursor metadata at the top level:
```json theme={null}
{
"success": true,
"result": [],
"pageInfo": {"prevCursor": "", "nextCursor": ""}
}
```
`limit` must be an integer from 1 to 1,000; the request default is 1,000. The service may cap historical page size further. Start without a cursor, then pass `pageInfo.nextCursor` as the next request's `cursor`, keeping filters unchanged. Stop when it is empty. Treat cursors as opaque strings and URL-encode them; do not decode or construct them. Do not infer completion solely from a page being shorter than your requested limit.
```python theme={null}
from spot_client import api
query = {"market": "SPY-USDC", "limit": 100}
while True:
page = api("GET", "/v1/spot/fills", query=query)
for fill in page["result"] or []:
print(fill["id"], fill["size"], fill["fee"])
cursor = page.get("pageInfo", {}).get("nextCursor")
if not cursor:
break
query["cursor"] = cursor
```
Persist fills by ID and reconcile an overlapping time window after interruptions. Separate REST reads do not provide a single atomic snapshot of orders, fills, and balances.
An error response normally has this shape:
```json theme={null}
{
"success": false,
"error": "Insufficient funds",
"error_code": "insufficient_funds"
}
```
Check both HTTP status and `success`. `result` can be omitted on errors or successful actions with no result, such as cancel-all. Preserve `error_code` for programmatic handling and treat `error` as descriptive text whose wording can change. Gateways or connection failures can return non-JSON errors.
| Error code | Client action |
| :- | :- |
| `timestamp_too_far`, `signature_mismatch` | Check clock, secret, timestamp units, encoded URL, method, and exact body bytes. |
| `key_doesnt_have_scope`, `ip_not_permitted` | Correct key permissions or egress allowlist. |
| `invalid_market`, `trading_disabled`, `feature_disabled` | Refresh configuration and confirm account/market availability. |
| `insufficient_funds` | Read Spot free balances and reservations. |
| `order_invalid_price`, `order_invalid_size` | Align price and base size to market increments. |
| `order_price_outside_safe_bounds` | Review the oracle reference, limit, and executable liquidity; see [Order price protection](/mark-price-protection). |
| `mark_price_not_available` | The reference price is unavailable; wait for valid pricing before resubmitting. This code is also used for Spot oracle availability. |
| `deprecated_field` | Remove unsupported `quoteSize`. |
| `invalid_market_order_fields` | Remove market-order `timeInForce`; omit price as well. |
| `post_only_has_match` | Choose a price that can rest, or change the intended order behavior. |
| `clientOrderID_collision` | Look up the original order by client ID. |
| `insufficient_liquidity`, `market_orders_disabled` | Refresh liquidity or use a supported order type. |
| `order_not_found`, `order_already_canceled`, `order_already_fully_filled` | Reconcile the order and fills before another mutation. |
| `too_many_open_orders`, `too_many_requests` | Reduce outstanding orders or request rate as appropriate. |
| `invalid_cursor` | Start a new bounded query and deduplicate persisted records. |
Back off with jitter on HTTP 429 and transient read failures. Respect `Retry-After` if present. For timeouts or server errors on writes, determine the original outcome before retrying.
## 10. Rate limits
**REST rate limits are ceilings, not recommended polling rates.** Clients should use WebSocket subscriptions for ongoing updates and keep REST reads limited to initialization, necessary lookups, and reconciliation. Limits can vary by account and environment; the standard defaults below are not a promise of throughput.
| Endpoint/action | Sustained rate | Burst |
| :- | :- | :- |
| Single-order placement | 30 requests/second | 60 |
| Single-order cancellation | 30 requests/second | 60 |
| Batch placement | 30 requests/second | 60 |
| Batch cancellation | 10 requests/second | 20 |
| Cancel all Spot orders | 2 requests/second | 5 |
| List orders | 10 requests/second | 60 |
| List fills | 30 requests/second | 15 |
| Create Spot transfer | 20 requests/hour | 20 |
| Other REST endpoints without a specific override | 10 requests/second | 60 |
Additional account-level budgets apply separately to order additions and cancellations: standard defaults are **30 order actions/second with a burst of 60** for each action. These budgets are shared across markets, placement paths, and Spot and Perps. A batch of N orders consumes N actions. Additional API keys for the same account do not create new account budgets. Cancel-all has its own endpoint budget and is exempt from the shared cancellation-action budget.
Avoid continuous REST polling for order books, trades, orders, fills, or balances when the corresponding WebSocket channel is available. Apply incoming updates to local state; do not issue a REST refresh after every WebSocket message or fill. Use occasional, bounded REST reconciliation to detect drift, and reconcile after reconnects or ambiguous writes.
If a required channel is unavailable, use conservative fallback polling, coalesce reads across your application's consumers, and back off on failures or HTTP 429. Stop fallback polling when the subscription is restored.
## 11. WebSocket authentication and subscriptions
Connect to `/ws` on the same environment as REST. Public market-data subscriptions can be used without login. Private channels require a successful login.
For API-key login, sign:
```text theme={null}
message = timestamp + "ondo_perps_ws_login"
signature = hex(HMAC_SHA256(secret_as_UTF8_bytes, message_as_UTF8_bytes))
```
The literal `ondo_perps_ws_login` is used for **Spot as well as Perps**. There is no path or body in the signed message. The timestamp uses milliseconds and the same acceptance window as REST.
Send `json.dumps(websocket_login())` from the Python helper over your WebSocket connection. Its JSON shape is:
```json theme={null}
{
"op": "login",
"args": {"key": "YOUR_KEY_ID", "time": "TIMESTAMP_IN_MILLISECONDS", "sign": "HEX_SIGNATURE"}
}
```
Wait for `type: "loggedIn"` before subscribing to private channels. A connection logs into one account. Reconnecting requires a new login and new subscriptions.
### Channels
| Channel | Access | Data in an `update` message |
| :- | :- | :- |
| `topOfBooksSpot` | Public | Array of best-bid/best-ask book snapshots. |
| `depthBooksSpot` | Public | Array of depth snapshots. |
| `tradesSpot` | Public | Array of public trades. |
| `kLineSpot` | Public | Candle updates for one market and resolution. |
| `ordersSpot` | Login | Array of your order objects. |
| `fillsSpot` | Login | Array of your fill objects. |
| `balanceSpot` | Login; where enabled | Complete array of your Spot balance rows. |
| `cancelAllOrdersAfterSpot` | Login | Arms or disarms cancellation on connection inactivity. |
Subscribe using `op: "subscribe"`. For book, order, fill, and trade subscriptions, specify the markets you need. Omitting `markets` subscribes to the channel's default market set; an explicit list makes the scope predictable. Unsubscribe with `op: "unsubscribe"` and the same channel and subscription parameters.
```json theme={null}
{"op":"subscribe","channel":"depthBooksSpot","markets":["SPY-USDC"],"limit":20}
```
```json theme={null}
{"op":"subscribe","channel":"ordersSpot","markets":["SPY-USDC"]}
```
```json theme={null}
{"op":"subscribe","channel":"fillsSpot","markets":["SPY-USDC"]}
```
```json theme={null}
{"op":"subscribe","channel":"balanceSpot"}
```
For depth subscriptions, `limit` caps the number of returned levels per side. Optional `depthLevels` is a **price grouping increment**, expressed as a decimal string, not a count of levels. Omit it or use `"0"` for ungrouped prices. An unsubscribe for a custom depth subscription must match its `depthLevels` and `limit`.
For `kLineSpot`, specify exactly one market and a resolution from `1`, `5`, `15`, `1H`, `4H`, `1D`, or `1W`. WebSocket hour resolutions differ from REST's `60` and `240`:
```json theme={null}
{"op":"subscribe","channel":"kLineSpot","markets":["SPY-USDC"],"resolution":"1H"}
```
The candle update's `data` is an object with `m` (market), `t` (event time), `s` (bar start), `e` (bar end), `o`/`h`/`l`/`c` (open/high/low/close), `v` (base volume), and `x` (whether the bar is closed). `t`, `s`, and `e` are Unix seconds. Unlike REST candles, WebSocket candle prices and volume are JSON numbers. Decode these numbers with a decimal-aware JSON parser, such as Python's `json.loads(message, parse_float=Decimal)`. Update the current bar by market and start time until it closes.
### Message handling
Responses use `type`, optional `channel`, `timestamp`, and optional `data`, `code`, or `msg`. Handle `loggedIn`, `subscribed`, `unsubscribed`, `update`, `pong`, and `error`.
Illustrative depth update:
```json theme={null}
{
"type": "update",
"channel": "depthBooksSpot",
"timestamp": "2026-09-14T12:00:00Z",
"data": [{
"market": "SPY-USDC",
"time": "2026-09-14T12:00:00Z",
"bids": [["599.99", "10"]],
"asks": [["600.01", "8"]]
}]
}
```
Book updates are **replacement snapshots**, not deltas. Replace the stored book for the relevant market and subscription when an update arrives. Do not add the incoming sizes to previous sizes. The feed does not expose a sequence-number/checksum contract for reconstructing missed changes.
`ordersSpot` and `fillsSpot` provide events, not a complete historical snapshot at subscription. Initialize open orders and historical fills through REST. `balanceSpot` provides complete balance snapshots and can coalesce multiple changes; it is not a transaction log. Replace your balance map when a new snapshot arrives. If the balance channel is unavailable, use REST balances alongside order and fill events.
Send an application-level heartbeat regularly, for example every 15 seconds, and monitor for a response:
```json theme={null}
{"op":"ping"}
```
The response has `type: "pong"`. Reconnect with backoff when the connection closes or heartbeats stop. A quiet market alone is not evidence of a broken connection.
### Recover after a disconnect
1. Reconnect, log in, and resubscribe. Discard book state from the old connection.
2. Buffer incoming private events while retrieving open orders, fills from an overlapping interval, balances, and transfers through REST.
3. Deduplicate fills by `id`. Merge order updates by `orderId`, preserving cumulative fills and terminal outcomes; requery an order if REST and buffered events disagree.
4. Refresh balances after reconciliation. These endpoints and channels do not share an atomic account snapshot or a global sequence number.
5. Resume order submission after the client has reconciled outstanding orders and funds.
## 12. Cancel on inactivity
Arm the Spot dead man's switch on the connection responsible for your trading session:
```json theme={null}
{"op":"subscribe","channel":"cancelAllOrdersAfterSpot","timeout_seconds":30}
```
It cancels **all open Spot orders for the authenticated account**, across markets, after the armed connection receives no client activity for the configured interval. Do not send `markets`. Inbound application messages, including `ping`, refresh the inactivity timer; outbound market updates do not. Keep heartbeats well inside the configured interval.
The timer remains armed if the connection closes. It is account-wide, so coordinate multiple clients: arming it on another connection replaces the account's previous Spot timer. It does not distinguish orders created by separate API keys or sessions. Do not rely on it as durable protection across service restarts; reconnect, reconcile, and re-arm it.
To disarm, send either:
```json theme={null}
{"op":"subscribe","channel":"cancelAllOrdersAfterSpot","timeout_seconds":0}
```
```json theme={null}
{"op":"unsubscribe","channel":"cancelAllOrdersAfterSpot"}
```
Disarming prevents future timer-triggered cancellation; it does not cancel existing orders. After a timeout or reconnect, read order status and fills to confirm which orders remain open. A fill can precede cancellation.
## 13. Market pauses and share adjustments
Affected markets can pause for corporate actions or other trading restrictions. Stop submitting new orders when the API reports that trading is unavailable. Preserve outstanding IDs and reconcile their final states before creating replacements.
Share adjustments can change a GM asset's balance without a fill. Read share-adjustment entries in Spot transfer history and refresh balances and market metadata after the event. Do not rescale historical fills using the current shares multiplier. Do not assume an old resting order was preserved, canceled, or resized solely because trading resumed; query it.
## 14. Additional REST exports
Authenticated CSV exports are available at `GET /v1/spot/orders/csv` and `GET /v1/spot/fills/csv`. These responses are CSV files, not the JSON envelope consumed by the Python helper. Order CSV export includes only orders with fills; it is not a source for the complete open-order set.
The trading integration covered here uses `/v1/spot/*`. Use the explicit Spot paths for order management, history, and market data.
# Create Spot transfer
Source: https://docs.ondoperps.xyz/api-reference/spot-balances-and-transfers/create-spot-transfer
/api-reference/rest-spec.json post /v1/spot/transfers
Move funds on the same account between main and spot or margin and spot. Margin moves require collateral eligibility and sufficient withdrawable margin. The returned record is the destination-crediting leg; from.wallet may be main for a margin/spot move. No client idempotency key is supported. Reconcile balances and history after an uncertain result before retrying.
See the [Spot API guide](/api-reference/spot-api-guide). Requires authentication; API-key scope: `transfer`.
# Get Spot balances
Source: https://docs.ondoperps.xyz/api-reference/spot-balances-and-transfers/get-spot-balances
/api-reference/rest-spec.json get /v1/spot/balances
Returns total, reserved, and free balances by coin.
See the [Spot API guide](/api-reference/spot-api-guide). Requires authentication; API-key scope: `view`.
# List Spot transfers
Source: https://docs.ondoperps.xyz/api-reference/spot-balances-and-transfers/list-spot-transfers
/api-reference/rest-spec.json get /v1/spot/transfers
Includes Spot movements, Spot subaccount transfers, and share adjustments. Excludes trading-fee transfers; use fills for fees.
See the [Spot API guide](/api-reference/spot-api-guide). Requires authentication; API-key scope: `view`.
# Get Spot candles
Source: https://docs.ondoperps.xyz/api-reference/spot-market-data/get-spot-candles
/api-reference/rest-spec.json get /v1/spot/candles
Returns OHLCV candlestick data for a Spot market as an array of objects, each with the full field names `startTime`, `open`, `high`, `low`, `close`, and `volume` (values are strings). If you need the TradingView UDF format instead (parallel `o`/`h`/`l`/`c`/`v` arrays of numbers), use `GET /v1/spot/history` (Get Price History). Supply market (candles) or symbol (history), resolution, to, and either from or countback. Resolutions: 1, 5, 15, 60, 240, 1D, 1W; D/W aliases are accepted. to is inclusive.
# Get Spot order book
Source: https://docs.ondoperps.xyz/api-reference/spot-market-data/get-spot-order-book
/api-reference/rest-spec.json get /v1/spot/depth
Book snapshot with levels serialized as [price, size] decimal-string arrays. Use depthBooksSpot for ongoing updates instead of polling.
See the [Spot API guide](/api-reference/spot-api-guide).
# Get Spot price history
Source: https://docs.ondoperps.xyz/api-reference/spot-market-data/get-spot-price-history
/api-reference/rest-spec.json get /v1/spot/history
Returns historical bar data in TradingView UDF format, as required by the TradingView charting library: a single object with parallel arrays keyed `t`, `o`, `h`, `l`, `c`, and `v` (values are numbers), plus a status field `s`. For a standard array-of-objects representation with full field names (`open`, `high`, `low`, `close`, `volume`), use `GET /v1/spot/candles` (Get Candles). Supply market (candles) or symbol (history), resolution, to, and either from or countback. Resolutions: 1, 5, 15, 60, 240, 1D, 1W; D/W aliases are accepted. to is inclusive.
# Get Spot symbol information
Source: https://docs.ondoperps.xyz/api-reference/spot-market-data/get-spot-symbol-information
/api-reference/rest-spec.json get /v1/spot/symbol_info
Returns symbol metadata in TradingView UDF format. See https://www.tradingview.com/rest-api-spec/#operation/getSymbolInfo.
# Get Spot tickers
Source: https://docs.ondoperps.xyz/api-reference/spot-market-data/get-spot-tickers
/api-reference/rest-spec.json get /v1/spot/ticker
Returns all configured Spot tickers. No market filter. Disabled markets can appear; inspect disabled. Authenticated requests reflect account-specific access. Volume, high, and low cover the trailing 24 hours.
See the [Spot API guide](/api-reference/spot-api-guide).
# List Spot trades
Source: https://docs.ondoperps.xyz/api-reference/spot-market-data/list-spot-trades
/api-reference/rest-spec.json get /v1/spot/trades
Market-wide executions, not your private fills. aggressor_side identifies the taker side.
See the [Spot API guide](/api-reference/spot-api-guide).
# Subscribe: Spot Balances
Source: https://docs.ondoperps.xyz/api-reference/spot-private-channels/subscribe:-spot-balances
/api-reference/ws-spec.json post /ws/balanceSpot
Requires login. Where enabled, balanceSpot delivers complete balance snapshots, not a transaction log. Replace the local balance map on every update. If unavailable, use GET /v1/spot/balances with conservative reconciliation. Send over the shared /ws connection. See the [Spot API guide](/api-reference/spot-api-guide).
# Subscribe: Spot Dead Man's Switch
Source: https://docs.ondoperps.xyz/api-reference/spot-private-channels/subscribe:-spot-dead-mans-switch
/api-reference/ws-spec.json post /ws/cancelAllOrdersAfterSpot
Subscribe to the `cancelAllOrdersAfterSpot` channel. Requires authentication (login first).
**Required**: `timeout_seconds` (the dead man's switch duration in seconds).
Send this subscription over the shared /ws connection; /ws/cancelAllOrdersAfterSpot is a documentation route, not a separate socket URL. See the [Spot API guide](/api-reference/spot-api-guide). Cancels all open Spot orders for the account after no client activity for timeout_seconds. Inbound ping refreshes the timer; outbound data does not. Omit markets. A new connection replaces the account timer. The timer remains armed after disconnect; re-arm after reconnect. Set timeout_seconds to 0 or unsubscribe to disarm without canceling existing orders.
# Subscribe: Spot Fills
Source: https://docs.ondoperps.xyz/api-reference/spot-private-channels/subscribe:-spot-fills
/api-reference/ws-spec.json post /ws/fillsSpot
Subscribe to the `fillsSpot` channel. Requires authentication (login first).
Optional `markets` to filter.
Send this subscription over the shared /ws connection; /ws/fillsSpot is a documentation route, not a separate socket URL. See the [Spot API guide](/api-reference/spot-api-guide). Events do not include a complete history at subscription. Initialize and reconcile through REST after reconnects.
# Subscribe: Spot Orders
Source: https://docs.ondoperps.xyz/api-reference/spot-private-channels/subscribe:-spot-orders
/api-reference/ws-spec.json post /ws/ordersSpot
Subscribe to the `ordersSpot` channel. Requires authentication (login first).
Optional `markets` to filter.
Send this subscription over the shared /ws connection; /ws/ordersSpot is a documentation route, not a separate socket URL. See the [Spot API guide](/api-reference/spot-api-guide). Events do not include a complete history at subscription. Initialize and reconcile through REST after reconnects.
# Subscribe: Spot Depth Book
Source: https://docs.ondoperps.xyz/api-reference/spot-public-channels/subscribe:-spot-depth-book
/api-reference/ws-spec.json post /ws/depthBooksSpot
Subscribe to the `depthBooksSpot` channel.
Optional `markets`, `depthLevels` (price grouping), and `limit` (max levels).
Send this subscription over the shared /ws connection; /ws/depthBooksSpot is a documentation route, not a separate socket URL. See the [Spot API guide](/api-reference/spot-api-guide). Updates are replacement snapshots, not deltas. Each level is a [price, size] array of decimal strings.
# Subscribe: Spot Kline / Candlesticks
Source: https://docs.ondoperps.xyz/api-reference/spot-public-channels/subscribe:-spot-kline-candlesticks
/api-reference/ws-spec.json post /ws/kLineSpot
Subscribe to the `kLineSpot` channel.
**Required**: exactly one `markets` entry and a `resolution`.
Valid resolutions: `1`, `5`, `15`, `1H`, `4H`, `1D`, `1W`.
Send this subscription over the shared /ws connection; /ws/kLineSpot is a documentation route, not a separate socket URL. See the [Spot API guide](/api-reference/spot-api-guide).
# Subscribe: Spot Top of Book
Source: https://docs.ondoperps.xyz/api-reference/spot-public-channels/subscribe:-spot-top-of-book
/api-reference/ws-spec.json post /ws/topOfBooksSpot
Subscribe to the `topOfBooksSpot` channel.
Optional `markets` to filter; omit for all markets.
Send this subscription over the shared /ws connection; /ws/topOfBooksSpot is a documentation route, not a separate socket URL. See the [Spot API guide](/api-reference/spot-api-guide). Updates are replacement snapshots, not deltas. Each level is a [price, size] array of decimal strings.
# Subscribe: Spot Trades
Source: https://docs.ondoperps.xyz/api-reference/spot-public-channels/subscribe:-spot-trades
/api-reference/ws-spec.json post /ws/tradesSpot
Subscribe to the `tradesSpot` channel.
Optional `markets` and `numPastTrades` (number of historical trades on subscribe).
Send this subscription over the shared /ws connection; /ws/tradesSpot is a documentation route, not a separate socket URL. See the [Spot API guide](/api-reference/spot-api-guide).
# Cancel all Spot orders
Source: https://docs.ondoperps.xyz/api-reference/spot-trading/cancel-all-spot-orders
/api-reference/rest-spec.json delete /v1/spot/orders
Cancel all open Spot orders, optionally limited to one market. Does not cancel Perps orders. A cancellation cannot undo a fill.
See the [Spot API guide](/api-reference/spot-api-guide). Requires authentication; API-key scope: `trade`.
# Cancel Spot batch
Source: https://docs.ondoperps.xyz/api-reference/spot-trading/cancel-spot-batch
/api-reference/rest-spec.json delete /v1/spot/orders/batch
Cancel 1–20 exchange IDs or client:-prefixed IDs. Inspect successfulCancels and failedCancels; this is not an atomic cancel-and-replace.
See the [Spot API guide](/api-reference/spot-api-guide). Requires authentication; API-key scope: `trade`.
# Cancel Spot order
Source: https://docs.ondoperps.xyz/api-reference/spot-trading/cancel-spot-order
/api-reference/rest-spec.json delete /v1/spot/orders/{orderID}
Cancel by exchange ID or client-prefixed ID. Reconcile status and fills after ambiguous results.
See the [Spot API guide](/api-reference/spot-api-guide). Requires authentication; API-key scope: `trade`.
# Export Spot fills
Source: https://docs.ondoperps.xyz/api-reference/spot-trading/export-spot-fills
/api-reference/rest-spec.json get /v1/spot/fills/csv
CSV export, not the JSON envelope. Execution history export.
See the [Spot API guide](/api-reference/spot-api-guide). Requires authentication; API-key scope: `view`.
# Export Spot orders
Source: https://docs.ondoperps.xyz/api-reference/spot-trading/export-spot-orders
/api-reference/rest-spec.json get /v1/spot/orders/csv
CSV export, not the JSON envelope. Only orders with fills are exported; this is not an open-order snapshot.
See the [Spot API guide](/api-reference/spot-api-guide). Requires authentication; API-key scope: `view`.
# Get Spot order
Source: https://docs.ondoperps.xyz/api-reference/spot-trading/get-spot-order
/api-reference/rest-spec.json get /v1/spot/orders/{orderID}
Look up by exchange ID or client-prefixed ID. An immediate not-found response after a timeout is not proof the original request cannot complete.
See the [Spot API guide](/api-reference/spot-api-guide). Requires authentication; API-key scope: `view`.
# Get Spot order fills
Source: https://docs.ondoperps.xyz/api-reference/spot-trading/get-spot-order-fills
/api-reference/rest-spec.json get /v1/spot/orders/{orderID}/fills
Returns all fills for one order without pagination.
See the [Spot API guide](/api-reference/spot-api-guide). Requires authentication; API-key scope: `view`.
# List Spot fills
Source: https://docs.ondoperps.xyz/api-reference/spot-trading/list-spot-fills
/api-reference/rest-spec.json get /v1/spot/fills
Your executions. Deduplicate by id; fee is denominated in the received asset.
See the [Spot API guide](/api-reference/spot-api-guide). Requires authentication; API-key scope: `view`.
# List Spot orders
Source: https://docs.ondoperps.xyz/api-reference/spot-trading/list-spot-orders
/api-reference/rest-spec.json get /v1/spot/orders
An open or canceled order may already have fills. Paginate using pageInfo.nextCursor.
See the [Spot API guide](/api-reference/spot-api-guide). Requires authentication; API-key scope: `view`.
# Place Spot batch
Source: https://docs.ondoperps.xyz/api-reference/spot-trading/place-spot-batch
/api-reference/rest-spec.json post /v1/spot/orders/batch
Submit 1–20 orders. Not all-or-nothing: inspect addedOrders and failedOrders even on HTTP success. Item failures use errorCode. Delayed placement uses /v1/spot/orders/batch/delayed.
See the [Spot API guide](/api-reference/spot-api-guide). Requires authentication; API-key scope: `trade`.
# Place Spot batch (delayed)
Source: https://docs.ondoperps.xyz/api-reference/spot-trading/place-spot-batch-delayed
/api-reference/rest-spec.json post /v1/spot/orders/batch/delayed
Submit 1–20 orders. Not all-or-nothing: inspect addedOrders and failedOrders even on HTTP success. Item failures use errorCode. Re-sign this delayed path when instructed by the server.
See the [Spot API guide](/api-reference/spot-api-guide). Requires authentication; API-key scope: `trade`.
# Place Spot order
Source: https://docs.ondoperps.xyz/api-reference/spot-trading/place-spot-order
/api-reference/rest-spec.json post /v1/spot/orders
Place a fully funded Spot limit or market order. Use base-denominated size; quoteSize is unsupported. Acknowledgement is not a fill guarantee. Reconcile an uncertain submission by clientOrderId before replacing it. Client IDs are not idempotency keys: accepted-ID reuse fails with clientOrderID_collision. Price protection uses the underlying oracle; see /mark-price-protection. Some flows require /v1/spot/orders/delayed; re-sign the new path when instructed.
See the [Spot API guide](/api-reference/spot-api-guide). Requires authentication; API-key scope: `trade`.
# Place Spot order (delayed)
Source: https://docs.ondoperps.xyz/api-reference/spot-trading/place-spot-order-delayed
/api-reference/rest-spec.json post /v1/spot/orders/delayed
Place a fully funded Spot limit or market order. Use base-denominated size; quoteSize is unsupported. Acknowledgement is not a fill guarantee. Reconcile an uncertain submission by clientOrderId before replacing it. Client IDs are not idempotency keys: accepted-ID reuse fails with clientOrderID_collision. Price protection uses the underlying oracle; see /mark-price-protection. Use this path when the server directs the account to delayed placement. Sign the exact delayed path with a fresh timestamp.
See the [Spot API guide](/api-reference/spot-api-guide). Requires authentication; API-key scope: `trade`.
# Get Stop Orders
Source: https://docs.ondoperps.xyz/api-reference/stop-orders/get-stop-orders
/api-reference/rest-spec.json get /v1/perps/stop_order
Gets a list of all stop orders for all markets. This section covers Position-Level stop orders only (take-profit and stop-loss attached to open positions). For TP/SL attached to limit orders, see the order creation payload (takeProfit, stopLoss, etc.). For how stop orders work, see [Take profit and stop loss](/take-profit-and-stop-loss).
# Remove Stop Order
Source: https://docs.ondoperps.xyz/api-reference/stop-orders/remove-stop-order
/api-reference/rest-spec.json delete /v1/perps/stop_order
Remove a stop order. If no type is specified, removes both stop loss and take profit. For how stop orders work, see [Take profit and stop loss](/take-profit-and-stop-loss).
# Set Stop Order
Source: https://docs.ondoperps.xyz/api-reference/stop-orders/set-stop-order
/api-reference/rest-spec.json post /v1/perps/stop_order
Set a stop order for a particular position defined by market and direction. Optionally pass builderCode to attribute the eventual triggered close to a builder for commission. For how stop orders work, see [Take profit and stop loss](/take-profit-and-stop-loss).
# Get Status
Source: https://docs.ondoperps.xyz/api-reference/tool/get-status
/api-reference/rest-spec.json get /status
Returns public API and system status.
# Hello
Source: https://docs.ondoperps.xyz/api-reference/tool/hello
/api-reference/rest-spec.json get /hello
Simple health check. Returns a static JSON payload.
# Cancel TWAP Order
Source: https://docs.ondoperps.xyz/api-reference/twap-orders/cancel-twap-order
/api-reference/rest-spec.json delete /v1/perps/twap/order/{orderID}
Cancels a running TWAP order. Child orders already placed are not affected. For how TWAP execution works, see [TWAP orders](/time-weighted-average-price).
# Create TWAP Order
Source: https://docs.ondoperps.xyz/api-reference/twap-orders/create-twap-order
/api-reference/rest-spec.json post /v1/perps/twap/order
Creates a new TWAP (Time-Weighted Average Price) order. The order is split into child orders placed at regular intervals over the specified running time. Running time must be between 5 minutes and 24 hours and a multiple of the frequency. The number of child orders (runningTime / frequency) must be between 2 and 200. For how TWAP execution works, see [TWAP orders](/time-weighted-average-price).
# Get Running TWAP Orders
Source: https://docs.ondoperps.xyz/api-reference/twap-orders/get-running-twap-orders
/api-reference/rest-spec.json get /v1/perps/twap/orders/running
Returns all currently running TWAP orders for the authenticated account. Optionally filter by market. For how TWAP execution works, see [TWAP orders](/time-weighted-average-price).
# Get TWAP Order by ID
Source: https://docs.ondoperps.xyz/api-reference/twap-orders/get-twap-order-by-id
/api-reference/rest-spec.json get /v1/perps/twap/order/{orderID}
Returns a single TWAP order by its ID. For how TWAP execution works, see [TWAP orders](/time-weighted-average-price).
# Get TWAP Order Fills
Source: https://docs.ondoperps.xyz/api-reference/twap-orders/get-twap-order-fills
/api-reference/rest-spec.json get /v1/perps/twap/order/{orderID}/fills
Returns all fills for all child orders of a TWAP order. For how TWAP execution works, see [TWAP orders](/time-weighted-average-price).
# Get TWAP Order History
Source: https://docs.ondoperps.xyz/api-reference/twap-orders/get-twap-order-history
/api-reference/rest-spec.json get /v1/perps/twap/orders/history
Returns a paginated list of completed or cancelled TWAP orders for the authenticated account. For how TWAP execution works, see [TWAP orders](/time-weighted-average-price).
# Edit Address Book Entry
Source: https://docs.ondoperps.xyz/api-reference/wallet/edit-address-book-entry
/api-reference/rest-spec.json put /v1/wallet/address_book
Update label for a withdrawal address.
# Export Deposits CSV
Source: https://docs.ondoperps.xyz/api-reference/wallet/export-deposits-csv
/api-reference/rest-spec.json post /v1/wallet/deposits/csv
Exports the account's deposit history as a downloadable CSV for the given time range. Columns: `id`, `time` (RFC 3339, UTC), `coin`, `size`, `status`. For supported assets and deposit steps, see [Funding Ondo Perps](/funding-ondo-perps).
# Export Withdrawals CSV
Source: https://docs.ondoperps.xyz/api-reference/wallet/export-withdrawals-csv
/api-reference/rest-spec.json post /v1/wallet/withdrawals/csv
Exports the account's withdrawal history as a downloadable CSV for the given time range. Columns: `time` (RFC 3339, UTC), `coin`, `size`, `address`, `status`. For the withdrawal process and timing, see [Withdrawals](/withdrawals).
# Get Address Book
Source: https://docs.ondoperps.xyz/api-reference/wallet/get-address-book
/api-reference/rest-spec.json get /v1/wallet/address_book
Returns withdrawal address book entries for the authenticated account.
# Get Deposit by ID
Source: https://docs.ondoperps.xyz/api-reference/wallet/get-deposit-by-id
/api-reference/rest-spec.json get /v1/wallet/deposits/{depositID}
Returns a single deposit by transaction ID. For supported assets and deposit steps, see [Funding Ondo Perps](/funding-ondo-perps).
# Get Withdrawal by ID
Source: https://docs.ondoperps.xyz/api-reference/wallet/get-withdrawal-by-id
/api-reference/rest-spec.json get /v1/wallet/withdrawals/{withdrawalID}
Returns a single withdrawal by transaction ID. For the withdrawal process and timing, see [Withdrawals](/withdrawals).
# Get Withdrawal Limits
Source: https://docs.ondoperps.xyz/api-reference/wallet/get-withdrawal-limits
/api-reference/rest-spec.json get /v1/wallet/withdrawals/limit
Returns withdrawal limits for the authenticated account. For the withdrawal process and timing, see [Withdrawals](/withdrawals).
# Get Withdrawal Status
Source: https://docs.ondoperps.xyz/api-reference/wallet/get-withdrawal-status
/api-reference/rest-spec.json post /v1/get_withdrawal_status
Get status of one or more withdrawals. For the withdrawal process and timing, see [Withdrawals](/withdrawals).
# List Deposit Addresses
Source: https://docs.ondoperps.xyz/api-reference/wallet/list-deposit-addresses
/api-reference/rest-spec.json post /v1/wallet/deposit_address/list
Returns the list of deposit addresses for the account. For supported assets and deposit steps, see [Funding Ondo Perps](/funding-ondo-perps).
# List Deposits
Source: https://docs.ondoperps.xyz/api-reference/wallet/list-deposits
/api-reference/rest-spec.json get /v1/wallet/deposits
Returns deposit history for the authenticated account. For supported assets and deposit steps, see [Funding Ondo Perps](/funding-ondo-perps).
# List Withdrawals
Source: https://docs.ondoperps.xyz/api-reference/wallet/list-withdrawals
/api-reference/rest-spec.json get /v1/wallet/withdrawals
Returns withdrawal history for the authenticated account. For the withdrawal process and timing, see [Withdrawals](/withdrawals).
# Provision Deposit Address
Source: https://docs.ondoperps.xyz/api-reference/wallet/provision-deposit-address
/api-reference/rest-spec.json post /v1/provision_address
Provision a new deposit address for a given symbol/chain. For supported assets and deposit steps, see [Funding Ondo Perps](/funding-ondo-perps).
# Remove Address Book Entry
Source: https://docs.ondoperps.xyz/api-reference/wallet/remove-address-book-entry
/api-reference/rest-spec.json delete /v1/wallet/address_book
Remove a withdrawal address from the address book.
# Withdraw
Source: https://docs.ondoperps.xyz/api-reference/wallet/withdraw
/api-reference/rest-spec.json post /v1/withdraw
Submit a withdrawal request. Rate limited per account. For the withdrawal process and timing, see [Withdrawals](/withdrawals).
# Architecture
Source: https://docs.ondoperps.xyz/architecture
Ondo Perps runs as an offchain matching engine with onchain settlement. The two are separate by design: the matching engine optimizes for speed and determinism; the settlement layer handles onchain asset custody.
***
## The Three Components
### Exchange-Engine
The Exchange-Engine housing the matching engine, account manager, order book, position tracking, and liquidation logic. It runs entirely offchain. When a user funds their account or withdraws, the exchange-engine queries the chain-core for onchain state, it does not interact with the chain directly.
#### Chain-Engine
The chain-core is the onchain interface. It processes funding and withdrawals: it watches for incoming onchain transfers, confirms them, and signals the exchange-engine to credit the account. For withdrawals, it executes the onchain transaction and pays gas on behalf of the user.
The chain-core holds no knowledge of internal accounts. It sees only onchain addresses and transfer amounts sweeping funds into the main hot wallet.
#### Attestor Network
The attestor network is the trust layer between the exchange-engine and the chain-core. It distributes the cryptographic secrets each component needs to operate, and serves as the consensus mechanism for blockchain state. Neither component can function fully without it.
***
## Execution Integrity
The matching engine runs inside an SGX (Software Guard Extensions) hardware enclave. SGX creates an isolated memory region that the operating system and the hardware owner cannot read or modify, including Ondo's own infrastructure team.
The enclave produces a cryptographic attestation proving it is running a specific, published binary. Reproducible builds ensure the binary is deterministic: the same source code produces the same binary every time, so the deployed code is independently verifiable against the published source.
The attestation model removes operator trust as a variable. No party, including Ondo, can modify matching logic or access order state while the enclave is running.
***
## Funding and Withdrawal Flow
**Funding:**
1. User sends USDC or a supported tokenized equity to their assigned funding address onchain
2. No bridge required, standard ERC-20 transfer
3. Chain-core detects the transfer and notifies the exchange-engine
4. Exchange-engine reflects the funding in the user's Perps balance
5. The chain-core sweeps the funds into its main hot wallet
**Withdrawal:**
1. User requests withdrawal via API
2. Exchange-engine checks the selected source balance and applicable withdrawal restrictions; withdrawals from Perps must also satisfy margin requirements
3. Chain-core executes the onchain transfer; Ondo pays gas
4. Withdrawal is marked as finalized when enough confirmations has been reached
USDC funding is supported on Ethereum and Arbitrum. Ondo tokenized stocks are supported on Ethereum. Funding addresses are specific to the asset and network.
***
## Collateral Custody
The chain-core handles on-chain funding and withdrawals through an omnibus wallet. The exchange-engine tracks each user's balances off-chain. Transfers between the user's Spot and Perps balances update those records without an on-chain transaction. The described architecture does not use a pooled smart contract to govern user funds. The trust guarantee is the SGX attestation, a cryptographic proof that the running binary matches the published source, not onchain contract auditability.
# Auto-Deleveraging (ADL)
Source: https://docs.ondoperps.xyz/auto-deleveraging
Auto-deleveraging (ADL) is the final safeguard, invoked when the Insurance Fund is depleted or when the mark price crosses the bankruptcy price. In this scenario, the exchange forcefully closes the position at bankruptcy price by filling against positions on the opposite side of the market.
Positions selected for ADL are ranked by profitability and effective leverage, the most profitable and most leveraged positions on the opposing side are deleveraged first.
**ADL Ranking**
`PnL Percentage = Unrealized PnL / Position Notional`
`Effective Leverage = Position Notional / Margin Balance`
| **Condition** | **Ranking Formula** |
| :- | :- |
| PnL Percentage >= 0 | `Ranking = PnL Percentage x Effective Leverage` |
| PnL Percentage \< 0 | `Ranking = PnL Percentage / Effective Leverage` |
A higher ranking indicates a higher likelihood of being selected for ADL. Traders can monitor their ADL ranking in the positions table.
# Balances
Source: https://docs.ondoperps.xyz/balances
The interface shows the user's holdings in two separate balances, Spot and Perps.
The user's **Spot balance** reflects assets available for spot trading or held following a spot purchase. The user's **Perps balance** reflects collateral allocated to support perpetual positions. For tokenized stocks, the interface shows an equivalent balance; the underlying tokenized stock remains the asset held by the user. An eligible collateral holding can be allocated partly to Spot and partly to Perps.
Funding is sent from the user's external wallet, and on-chain withdrawals return assets to an external wallet. Moving an available amount between the user's Spot and Perps balances is an internal transfer. It does not send tokens on-chain or execute a trade.
## Total, available and withdrawable
Total is the full quantity reflected in the selected balance. For tokenized stocks, it is shown as the displayed equivalent quantity. Open orders and pending withdrawals can reserve part of it. Available excludes the holds relevant to a given action, so an amount available to trade is not necessarily available to withdraw.
Perps withdrawals and transfers also depend on margin requirements. A combined portfolio total is a display figure, not an amount available to spend, transfer or withdraw.
## Perps risk processes and the Spot balance
Liquidation, Auto-Exchange and auto-deleveraging apply only to the user's Perps balance and open perpetual positions. These processes do not sell, lock or draw on assets allocated to the user's Spot balance.
Eligible assets allocated to the user's Perps balance can be used as collateral. Where the same tokenized stock is reflected in both balances, Auto-Exchange can sell the portion allocated to Perps to cover a USDC shortfall. The portion allocated to Spot is unaffected by that sale. Changes to the Perps balance can change the combined portfolio total even when the Spot holding is unchanged.
For an eligible collateral asset, allocation to the user's Spot or Perps balance determines whether it supports perpetual positions. Ondo Private Markets Notes are not eligible as Perps collateral and do not contribute margin. See [Private Markets](/private-markets).
## Haircuts
Haircuts and collateral caps apply to collateral allocated to the user's Perps balance. Assets allocated to the user's Spot balance are not subject to a haircut. See [Collateral Value](/collateral-value).
## When funds appear in neither balance
An interrupted funding or transfer may require support to locate the transaction and arrange recovery. An investigation uses the asset, amount, approximate time, intended balance and the funding transaction or transfer reference. A missing displayed balance does not confirm that the action failed or that a retry is safe.
# Changelog
Source: https://docs.ondoperps.xyz/changelog
Product and documentation updates for Ondo Perps
**Platform updates**
* **Eligible collateral expanded** to include NVDAon, GOOGLon, TSLAon, SNDKon, SPCXon, CRCLon, SLVon, and GLDon, alongside SPYon and QQQon, each with its own haircut and collateral cap.
* **The interface now displays funded tokenized stock as an equivalent balance** under the corresponding asset symbol (e.g. SPYon shows as a SPY-equivalent balance), calculated using a reference price. The underlying token is unchanged and returned in full on withdrawal.
**Documentation updates**
* **Funding Ondo Perps:** renamed from Funding Your Account; published the full list of eligible collateral, haircuts, and caps.
* **Collateral Assets:** new page documenting the funding-asset-to-displayed-balance mapping and withdrawal behavior.
* **Overview, Withdrawals, Collateral Value, Settlement, Collateral Management, Positions Panel:** clarified that the displayed asset symbol (e.g. SPY) is a UI representation of the underlying tokenized stock, not a separate asset.
**Platform updates**
* **Tokenized Stock Collateral is now available to everyone.** The Pre-Alpha restriction has been removed; fund with SPYon or QQQon from a wallet.
* **API key creation is enabled by default** for all accounts. API usage is rate limited to 1 request per second by default; higher limits are available by request via [support@ondoperps.xyz](mailto:support@ondoperps.xyz).
* **Maximum position size raised to \$1,000,000** per market, per account (previously \$500,000).
**Documentation updates**
* **Public Beta:** documented the Tokenized Stock Collateral beta parameters: per-asset collateral caps, the 30% maximum allowed USDC Debt, and the \$100,000 USDC Debt cap per account.
* **Funding Your Account:** published per-asset collateral caps (146 SPYon, 166 QQQon).
* **Fees:** clarified fee mechanics: fees are charged in USDC per fill and deducted from your USDC balance, separate from position PnL.
* **Funding Rates:** clarified that funding payments are credited to or debited from your USDC balance.
* **Collateral Management:** corrected the Maximum Allowed Debt formula to `min(Non-USDC Margin Value x 30%, $100,000)`.
* **Settlement:** documented that Auto-Exchange also triggers at the \$100,000 USDC Debt maximum, and clarified the difference between Auto-Exchange and liquidation. Auto-Exchange sells only enough collateral to cover the debt.
* **Withdrawals:** published the exact withdrawal model, including how unrealized PnL and USDC Debt affect what you can withdraw.
* **Weekend Mechanics:** stated the closed-market settlement fee (currently 2.5% of the debt repaid).
* **Measuring Risk:** noted that with tokenized stock collateral, a decline in collateral price can raise your Margin Ratio on open positions.
# Collateral Assets
Source: https://docs.ondoperps.xyz/collateral-assets
Each eligible Ondo tokenized stock is accepted as collateral on Ondo Perps. For readability, the interface displays the value of the user's funded Ondo Stock as an equivalent balance under the corresponding asset symbol tradable on Ondo Perps, using a reference price. The displayed balance is a UI representation; it does not convert the tokenized stock into a separate asset. The underlying tokens remain unchanged and are returned as the same tokenized stock amount when withdrawn, subject to changes in the total collateral amount caused by user trading.
| Asset used to fund | Displayed in UI | Asset returned on withdrawal |
| :- | :- | :- |
| USDC | USDC | USDC |
| SPYon | SPY equivalent | SPYon |
| QQQon | QQQ equivalent | QQQon |
| NVDAon | NVDA equivalent | NVDAon |
| GOOGLon | GOOGL equivalent | GOOGLon |
| TSLAon | TSLA equivalent | TSLAon |
| MUon | MU equivalent | MUon |
| SNDKon | SNDK equivalent | SNDKon |
| DRAMon | DRAM equivalent | DRAMon |
| SPCXon | SPCX equivalent | SPCXon |
| CRCLon | CRCL equivalent | CRCLon |
| SLVon | SLV equivalent | SLVon |
| GLDon | GLD equivalent | GLDon |
SPY, QQQ, and the other symbols shown above are balance labels in the interface, not separate assets or perpetual markets. Perpetual market symbols carry a `.P` suffix (for example, `AAPL-USD.P`); Spot markets use pairs such as `TSLA/USDC`. A Spot purchase is of the corresponding GM token.
Ondo Private Markets Notes are spot assets and are **not eligible as Perps collateral**. Funding a Spot account with a note or buying one does not provide margin for perpetual positions. See [Private Markets](/private-markets).
## How funding and withdrawals are reflected
**When funding:** the interface calculates the displayed equivalent quantity by multiplying the funded token quantity by the applied S value. Funding 10 SPYon at S = 1.02 displays 10.20 SPY equivalent. The underlying asset remains SPYon.
**When withdrawing:** the requested displayed equivalent quantity is used to calculate the corresponding quantity of the underlying tokenized stock. The token quantity sent is the requested displayed quantity divided by the applied S value, rounded down to the supported token precision. Withdrawing 10.20 SPY equivalent at S = 1.02 sends 10 SPYon to the user's external wallet, subject to applicable withdrawal requirements.
The underlying tokenized stock remains the asset held by the user. The interface displays an equivalent balance under the corresponding asset symbol using the asset's applied S value. The displayed quantity can differ from the token quantity and can change as the S value changes. This is a display calculation; it does not convert the tokenized stock into shares or create a separate asset.
Spot trading and the Spot balance are described on [Spot trading](/spot-trading) and [Balances](/balances).
# Collateral Management
Source: https://docs.ondoperps.xyz/collateral-management
When your trading costs exceed your USDC balance, your balance goes negative. This happens naturally as you trade: fees, funding payments, and realized losses are all settled in USDC. If you don't have enough USDC to cover them, the shortfall carries as a negative balance.
This negative balance is your **USDC Debt**. Your tokenized stock collateral backs it, so you can keep trading without topping up USDC after every loss.
```text theme={null}
USDC Debt = margin balance - Non-USDC Margin Value
```
When this number is negative, you have debt. When it is zero or positive, you have no debt.
### **LTV (Loan-to-Value)**
LTV tells you how much of your collateral's credited value is consumed by debt:
```text theme={null}
LTV = abs(USDC Debt) / Non-USDC Margin Value
```
* **LTV 0%:** No debt
* **LTV 10%:** Your debt equals 10% of your credited collateral
* **LTV 30%:** Auto-Exchange triggers to clear your debt
Think of LTV as a health gauge. As your debt grows or your collateral value drops, LTV rises. At 30%, Auto-Exchange steps in.
### **Maximum Allowed Debt**
Your maximum allowed debt is the lesser of two limits: 30% of your Non-USDC Margin Value, and a hard cap of \$100,000:
```text theme={null}
Allowed USDC Debt = min(Non-USDC Margin Value x 30%, $100,000)
```
Whichever limit you hit first applies. For example, if your Non-USDC Margin Value is \$90,000, your maximum allowed debt is \$27,000 (30% x \$90,000). If your Non-USDC Margin Value is \$400,000, the LTV limit would allow \$120,000, but the \$100,000 cap applies instead.
Both limits are current Public Beta risk parameters and may be adjusted over time.
### **What Creates USDC Debt**
These reduce your USDC balance and create debt:
* **Trading fees:** Charged per fill and deducted from your USDC balance, separate from position PnL. Opening a position does not create a profit or loss by itself; any immediate balance change is the trading fee.
* **Funding payments:** Charged on open positions when your side of the market pays
* **Realized losses:** Settled when you close a losing trade
### **What Raises Your LTV**
These do not change your debt, but they push your LTV higher, bringing you closer to the 30% Auto-Exchange threshold:
* **Your collateral price drops:** Your Non-USDC Margin Value decreases, so the same debt represents a larger share of your collateral.
* **Unrealized losses on open positions:** These reduce your overall margin balance, widening the gap between your margin balance and your collateral value.
You can have no new realized losses and still see your LTV rise if your collateral price drops or your open positions move against you.
### **If Your Debt Gets Too Large**
If your LTV reaches 30%, Auto-Exchange triggers and sells enough of your collateral to clear your debt entirely. See the next section for how this works.
# Collateral Value
Source: https://docs.ondoperps.xyz/collateral-value
A haircut is the discount applied to your collateral's market value when calculating your credited margin.
Haircuts and collateral caps apply to collateral allocated to the user's Perps balance. Assets allocated to the user's Spot balance are not subject to a haircut.
**Example:** A 10% haircut on a \$1,000 SPY-equivalent balance means the exchange credits you with \$900 of margin. The haircut does not reduce the underlying token quantity. Withdrawal availability remains subject to margin requirements and other applicable restrictions; the haircut affects only how much of the displayed value counts toward margin, absorbing price volatility and the cost of converting collateral to USDC if Auto-Exchange or liquidation is ever triggered.
**Key details:**
* Collateral ratio equals 1 minus the haircut (10% haircut = 0.90 collateral ratio).
| Term | Formula |
| :- | :- |
| Credited Margin (per asset) | `displayed equivalent quantity x reference price x (1 - haircut)`, subject to the asset cap |
| Collateral Ratio | `1 - haircut` |
For details on accepted collateral see [Funding Ondo Perps](/funding-ondo-perps).
## **How Margin Is Calculated**
Perps margin health is represented by a single number called **margin balance**:
```text theme={null}
margin balance = USDC balance + Non-USDC Margin Value + unrealized PnL + funding
```
Where:
* **Non-USDC Margin Value:** the total post-haircut value of eligible tokenized stocks allocated to the user's Perps balance, subject to each asset's cap. For a reference price quoted per equivalent unit, the calculation uses the displayed equivalent quantity × reference price × (1 − haircut). The displayed equivalent quantity is calculated from the underlying token quantity and applied S value.
* **USDC balance:** The user's Perps USDC balance, which can be negative because of losses or fees.
* **Unrealized PnL:** Profit or loss on open perpetual positions.
* **Funding:** Accumulated perpetual funding payments.
Your margin balance captures everything that affects Perps margin health in one number. It moves when asset prices change, when your positions gain or lose value, when you pay fees, and when funding payments accrue.
# Contact
Source: https://docs.ondoperps.xyz/contact
Contact the Ondo Perps support team via the trading app (bottom right of the app) or at [support@ondoperps.xyz](mailto:support@ondoperps.xyz).
## Troubleshooting
Provide the details below when contacting support. Never share a seed phrase, private key, password or API secret.
| Symptom | What to provide |
| :- | :- |
| An order was rejected despite an available balance | Market, side, quantity, the relevant available amount, time, and the rejection or order reference |
| A withdrawal amount differs from the amount requested | The displayed equivalent quantity requested, the underlying token quantity received, asset and network, and the request and transaction references |
| A displayed equivalent quantity changed without a trade | Underlying tokenized stock, before and after displayed equivalent quantities, approximate time, and any other activity in the interval |
| Funding or a transfer is missing from both balances | Asset, amount, intended balance and direction, time, and the funding or transfer reference |
| A withdrawal remains pending | Source balance, asset and network, requested quantity, time, request reference and any transaction reference |
A pending status is not confirmation of receipt, and an unknown outcome is not confirmation that a retry is safe. Request a status review before submitting a second request.
# Corporate actions
Source: https://docs.ondoperps.xyz/corporate-actions
Dividends and stock splits can change the S value of an Ondo tokenized stock and the equivalent balance displayed on Ondo Perps. The underlying GM token remains the asset held by the user.
## The S value
The S value measures the underlying share exposure represented by each GM token. Ondo Perps uses the applied S value to calculate the displayed equivalent quantity: token quantity multiplied by S.
For example, 10 SPYon with an applied S value of 1.02 is displayed as 10.20 SPY equivalent. The user holds SPYon. The SPY equivalent balance is a display representation, not a separate holding of shares.
When a dividend or split changes S, the displayed equivalent quantity changes. That adjustment alone does not change the number of GM tokens held.
| Item | Effect of a dividend or split adjustment |
| :- | :- |
| Underlying GM token, for example SPYon | Remains the asset held by the user |
| Number of GM tokens | Unchanged by the adjustment alone |
| S value | Changes |
| Displayed equivalent quantity, for example SPY equivalent | Changes with the applied S value |
| Collateral value and contribution to margin | Depend on the applicable reference price and collateral rules |
The adjustment is reflected in the affected tokenized stock's displayed balances, including the user's Spot and Perps balances. No user action is required for the display adjustment.
## Dividends
Cash dividends are reinvested within the underlying tokenized stock and reflected in its S value. When the updated S value is applied on Ondo Perps, the displayed equivalent balance increases. The adjustment does not increase the number of GM tokens held or credit a separate cash or USDC payment to the user's wallet.
A dividend adjustment does not cancel resting orders. Open orders keep their original size; the increase in displayed equivalent quantity is reflected in the available balance.
Example: 100 SPYon at S = 1.000 is displayed as 100 SPY equivalent. If the applied S value increases to 1.004 following a dividend, the display becomes 100.4 SPY equivalent. The underlying holding remains 100 SPYon, assuming no other activity.
A change in the displayed equivalent balance does not by itself establish a change in collateral value. The amount that contributes to margin depends on the applicable reference price and collateral rules.
## Stock splits
A forward split increases S by the split ratio; a reverse split reduces it. The displayed equivalent quantity changes with S. The corresponding price per equivalent unit reflects the split.
Example: 10 NVDAon at S = 1.000 is displayed as 10 NVDA equivalent. At a reference price of USD 1,200 per equivalent unit, the displayed value is USD 12,000. A 10-for-1 split changes S to 10.000 and the display to 100 NVDA equivalent. At the corresponding split-adjusted reference price of USD 120, the displayed value remains USD 12,000. The user still holds 10 NVDAon. This example assumes no other activity or market-price change.
During a split adjustment:
* Ondo Stocks can pause token transfers while the split is processed.
* Ondo Perps holds the affected collateral reference price fixed during its adjustment window. Liquidation continues to run against the held price.
* Funding and withdrawals on Ondo Perps remain subject to their usual checks and applicable transfer availability.
* Resting orders in the affected spot market are cancelled before the adjustment. Replacement orders can be placed after the market reopens.
## Reverse splits
A reverse split reduces the displayed equivalent quantity without reducing the number of underlying GM tokens held through the adjustment alone. Resting orders must be cancelled and in-flight withdrawals must settle before the adjustment can proceed for the affected asset. Amounts reserved for those actions must first be released.
# Liquidations and Insurance
Source: https://docs.ondoperps.xyz/def
## **Overview**
Liquidation occurs when your margin balance falls below your total maintenance margin requirement. Ondo Perps uses cross margin, meaning all perpetual positions share the collateral allocated to the user's Perps balance. A loss on one position affects overall Perps margin health.
The liquidation system is designed to close positions at the best available price on the order book before resorting to more aggressive mechanisms. Each stage of the process prioritizes retaining as much of your remaining capital as possible.
**Auto-Exchange is not liquidation.** Auto-Exchange sells tokenized stock collateral to clear USDC Debt and leaves your positions **open**. Liquidation closes your perpetual positions (partially by default) and does not directly sell your collateral tokens. See [Settlement](/settlement) for how Auto-Exchange works.
Liquidation acts on collateral allocated to the user's Perps balance and on the user's perpetual positions. Assets allocated to the user's Spot balance are not used for that liquidation. See [Balances](/balances).
***
## **Margin and Liquidation Trigger**
### **Maintenance Margin**
Each market has its own maintenance margin rate, tiered by position size. Larger positions require proportionally more margin. Example rates: 2.5% for liquid markets, 5% for less liquid ones.
The formula for a single position:
`maintenance_margin = position_notional * maintenance_margin_rate - maintenance_amount`
The `maintenance_amount` is a smoothing term that prevents sudden jumps when a position crosses from one size tier to the next. Your total maintenance margin is the sum across all open positions.
### **Liquidation Trigger**
Liquidation triggers when:
`margin_balance < total_maintenance_margin`
Where `margin_balance = wallet_balance + unrealized_pnl`.
***
## **Liquidation Price**
The liquidation price is the mark price at which your margin balance would fall below the maintenance margin requirement. For a long position, it is below your entry price. For a short, it is above.
### **Formula**
`liq_price = (margin_balance + maintenance_amount - other_positions_maintenance - side_notional) / (position_quantity * (maintenance_margin_rate +/- 1))`
Where:
| **Variable** | **Definition** |
| :- | :- |
| `margin_balance` | wallet balance + unrealized PnL across all positions |
| `maintenance_amount` | tier smoothing coefficient for this position's market |
| `other_positions_maintenance` | total maintenance margin from all other open positions |
| `side_notional` | position notional (positive for longs, negative for shorts) |
| `position_quantity` | size of the position |
| `maintenance_margin_rate` | rate for this position's size tier |
| `+/-` | minus 1 for longs, plus 1 for shorts |
Because Ondo Perps uses cross margin, your liquidation price on any position depends on the user's Perps balance and other perpetual positions, their unrealized PnL, and total maintenance requirements.
A negative liquidation price (possible for well-collateralized longs) is displayed as zero.
***
## **Worked Example**
A trader funds \$1,000 USDC and opens a 10x long on NVDA at \$130, buying 76.92 shares (\$10,000 notional).
| **Metric** | **Value** |
| :- | :- |
| Position notional | \$10,000 |
| Leverage | 10x |
| Initial margin (10%) | \$1,000 |
| Maintenance margin (5%) | \$500 |
| Margin ratio at entry | 50% |
### **NVDA drops to \$126.50**
Unrealized loss: 76.92 x (\$130 - \$126.50) = \~\$269
| **Metric** | **Value** |
| :- | :- |
| Margin balance | \$731 |
| Maintenance margin | \$487 |
| Margin ratio | 66.6% |
Not yet in liquidation, but margin ratio is rising.
### **NVDA drops to \$123.50**
Unrealized loss: 76.92 x (\$130 - \$123.50) = \~\$500
| **Metric** | **Value** |
| :- | :- |
| Margin balance | \$500 |
| Maintenance margin | \$475 |
| Margin ratio | 95% |
Close to liquidation. Funding USDC or reducing the position would lower the margin ratio.
### **NVDA drops to \$123.00**
Unrealized loss: 76.92 x (\$130 - \$123.00) = \~\$538
| **Metric** | **Value** |
| :- | :- |
| Margin balance | \$462 |
| Maintenance margin | \$473 |
| Margin ratio | 102% |
Margin balance has fallen below maintenance margin. **Liquidation is triggered.** New perpetual orders are disabled, resting perpetual orders are cancelled, and partial liquidation of the NVDA position begins.
***
## **Liquidation Process**
When liquidation triggers, the system follows a multi-stage process. Each stage is more aggressive than the last, and the system stops as soon as the user's Perps margin balance is restored above maintenance margin.
### **Stage 1: Order Cancellation**
New perpetual orders are disabled and resting perpetual orders are cancelled. Resting orders reserve margin but do not affect your liquidation price, so cancelling them may free enough margin to prevent position liquidation.
### **Stage 2: Liquidation via Order Book**
The system sends limit orders to the order book to close your positions, starting with the most liquid market. Positions above \$1,000 in notional value are liquidated partially, approximately 10% of the position at a time, with 5-second intervals between each chunk. This gives the book time to absorb the size and reduces market impact.
Positions at or below \$1,000 notional are liquidated in full.
A **1.5% liquidation fee** is charged on the filled notional value and sent to the insurance fund.
Where the order book cannot absorb the liquidation, an announcement is issued and a backstop liquidator may take the position at bankruptcy price alongside the retry loop.
### **Stage 3: Insurance Fund Assistance**
If the position has not been fully closed after approximately 1 minute of retries, the insurance fund activates. It widens the acceptable liquidation price up to **5% worse than the current mark price**, making it easier for the order to fill against available book liquidity.
The insurance fund also activates immediately if the mark price crosses the **bankruptcy price** (the price at which your margin balance would equal zero), regardless of how much time has passed.
### **Stage 4: Auto-Deleveraging (ADL)**
If approximately 2 minutes have passed and the insurance fund balance is effectively exhausted (below \$100), the system triggers auto-deleveraging as a last resort. See the ADL section below for details.
### **Escalation Timeline**
| **Time** | **What Happens** |
| :- | :- |
| 0 seconds | Resting perpetual orders cancelled, new perpetual orders disabled, first liquidation attempt on the book |
| Every 5 seconds | Retry with a new limit order if previous attempt did not fully fill |
| 1 minute | Insurance fund activates, widening the limit price up to 5% worse than mark |
| 2 minutes | ADL activates if the insurance fund is exhausted |
***
## **Insurance Fund**
The insurance fund is a dedicated pool of capital that backstops the liquidation system.
**How it's funded:** Every liquidation charges a 1.5% fee on filled notional value. This fee is transferred to the insurance fund.
**When it's used:** After 1 minute of failed liquidation attempts (or immediately at bankruptcy), the insurance fund subsidizes wider limit prices so liquidation orders can fill against deeper book liquidity. There is a per-event cap on how much the fund will spend on a single liquidation.
**What it protects:** The fund absorbs losses when a liquidated account goes negative (losses exceed collateral). Without it, those losses would fall on other traders.
***
## **Auto-Deleveraging (ADL)**
ADL is the system's final safeguard for platform solvency. It is triggered only when normal liquidation and the insurance fund have failed to close a position.
### **When ADL Triggers**
ADL activates under two conditions:
1. **Bankruptcy:** The mark price crosses the bankruptcy price (the position is underwater beyond its collateral), regardless of time elapsed.
2. **Insurance exhaustion:** More than 2 minutes have passed since liquidation started, and the insurance fund balance is below \$100.
### **How Counterparties Are Selected**
The system identifies all traders holding positions in the same market on the opposite side of the liquidated position. These traders are ranked by a combination of their profitability and leverage:
`rank = pnl_percentage * effective_leverage` (for profitable positions)
`rank = pnl_percentage / effective_leverage` (for unprofitable positions)
Where:
* `pnl_percentage = unrealized_pnl / position_notional`
* `effective_leverage = position_notional / margin_balance`
Traders with the highest rank (most profitable and most leveraged) are deleveraged first. Their positions are closed at the bankruptcy price against the liquidated account.
Each counterparty's fill size is constrained so they remain above maintenance margin after the ADL fill.
***
## **Mark Price**
Liquidations use **mark price**, not the last traded price or a single book price. Mark price is designed to resist manipulation and provide a stable reference for margin calculations.
Mark price is the **median of three components**:
1. **Oracle price:** External price feeds from centralized exchanges.
2. **Oracle price + drift:** The oracle price adjusted by an exponentially weighted moving average of how much the on-platform orderbook mid-price diverges from the oracle (smoothed over \~150 seconds).
3. **Book composite:** The median of the best bid, best ask, and last trade on the Ondo Perps book.
Taking the median of these three values means no single component can drive the mark price on its own. Mark price movement is also rate-limited per update to prevent sudden spikes.
During weekends or when underlying markets are closed, the system switches to internal price discovery using orderbook activity, with bounded movement limits.
***
## **Cross Margin**
Ondo Perps uses cross margin exclusively. All perpetual positions share the collateral allocated to the user's Perps balance.
**What this means for liquidation:**
* A loss on any position reduces the margin available for all other positions
* Your liquidation price on one position depends on the user's Perps balance and other perpetual positions
* When liquidation triggers, all resting perpetual orders are cancelled and all perpetual positions become eligible for forced closure
* Closing a profitable position frees margin that can protect your losing positions
Ondo Perps does not support isolated margin. This is a deliberate design choice: cross margin is more capital-efficient for most traders, as gains on one position automatically offset losses on another without requiring manual rebalancing.
***
## **Avoiding Liquidation**
If your margin ratio is rising toward 100%, you have several options:
* **Fund more USDC or tokenized stock:** Increases your margin balance directly.
* **Close or reduce positions:** lowers your maintenance margin requirement.
* **Set stop-loss orders:** Automatically exits positions before the mark price reaches your liquidation price.
* **Close profitable positions:** In cross margin, this frees margin for your remaining positions.
Monitor your margin ratio and liquidation price. During high volatility, mark price can move quickly, and the displayed liquidation price is an estimate that can shift as your unrealized PnL and other positions change. Ondo Perps uses a multi-layered liquidation process to protect both traders and the platform during periods of market volatility, ensuring positions are closed fairly while maintaining platform stability.
# Fees
Source: https://docs.ondoperps.xyz/fees
Ondo Perps charges maker and taker fees on completed fills. Perpetual trading fees and liquidation fees are charged in USDC from the user's Perps balance. Ondo Global Markets Spot fees are taken from the asset received in the fill, as described below. Private Markets has a separate trading fee schedule.
## Perpetual trading fees
**Promotion: For a limited time, base trading fees are discounted 50% off and are set as follows:**
| **Fee Type** | **~~Base Rate~~ Promotional Rate (50% off)** |
| :- | :- |
| Maker | ~~0.02% (2 bps)~~ 0.01% (1bp) |
| Taker | ~~0.05% (5 bps)~~ 0.025% (2.5bps) |
Fees are calculated on the filled notional value (price x quantity) and assessed in USDC. These are the default perpetual trading rates. Actual fees may be lower depending on your 14-day rolling trading volume (volume-based fee tiers) or individual fee arrangements. Referral rebates reduce the net perpetual trading fee.
## Spot trading fees
This section describes Ondo Global Markets Spot trading. The launch promotion below does not apply to Private Markets; see [Private Markets trading fees](#private-markets-trading-fees).
**Launch promotion: Spot maker and taker trading fees are 0% for the first 30 days after Spot launch.**
| Fee type | Launch promotional rate |
| :- | :- |
| Maker | 0% |
| Taker | 0% |
The promotion runs from Spot launch, not from your first trade. Check your authenticated `GET /v1/markets` response under `result.spot.tradingPairs` for the applicable `makerFee` and `takerFee`. Rates are decimal fractions: `0` means 0%, and `0.0001` means 0.01%. See the [Spot API guide](/api-reference/spot-api-guide).
Spot fees apply to completed fills in the user's Spot balance:
| Fill | Fee asset | Effect |
| :- | :- | :- |
| Buy | Purchased GM token | Reduces the purchased holding and its displayed equivalent quantity |
| Sell | USDC | Reduces the USDC proceeds received |
For a buy, the fee is calculated from the filled equivalent quantity and the applicable fee rate. For a sell, it is calculated from the USDC proceeds and the applicable fee rate.
Spot fee rates are set by market and may differ under individual fee arrangements. The perpetual promotional rates and volume tiers above do not determine Spot fees.
## Private Markets trading fees
Private Markets has its own trading fees. Do not assume that the fee rates for perpetual markets or other spot markets apply. The introductory rates for OPAIPon are:
| Fee type | Introductory rate |
| :- | :- |
| Maker | 0.25% (25 bps) |
| Taker | 0.50% (50 bps) |
Fees are subject to change and may differ for future listings. Trading fees are separate from any tax withholding or settlement fees deducted from an eventual redemption payout by the Issuer. See [Private Markets](/private-markets) for trading mechanics.
## Liquidation Fee
A **1.5% fee** is charged on the notional value of any liquidated position. This fee goes directly to the Insurance Fund. The fee is reduced if charging the full amount would push the user's Perps margin balance below maintenance margin, and is set to zero if the user's Perps margin balance is not positive.
## Fee Distribution
Trading fees and liquidation fees flow to two separate destinations:
| **Fee Type** | **Destination** |
| :- | :- |
| Maker/taker trading fees | Exchange Fee Account (exchange revenue) |
| Liquidation fees (1.5%) | Insurance Fund |
There is no automated split between trading revenue and the insurance fund. They are independent streams.
For perpetual trading fees, the protocol may pay out from the Exchange Fee Account:
* **Referral commissions** to referrers (percentage of the trading fee)
* **Referral rebates** back to referred traders (percentage of the trading fee)
* **Builder commissions** to API integrators (incremental fee up to 10 bps, added on top of the base maker/taker fee and paid to the builder)
## Insurance Fund
The Insurance Fund is funded exclusively by liquidation fees. It serves as a backstop when liquidation cannot fill at a viable price:
* Activates after \~60 seconds of failed liquidation attempts; or
* Immediately if the mark price crosses the bankruptcy price for the position being liquidated; and
* Can widen the acceptable liquidation price up to **5% worse than the mark price.**
## Learn More
Hourly funding mechanism, formula, scaling factors, and worked examples.
The multi-layered liquidation waterfall and how the insurance fund is funded.
# Funding Ondo Perps
Source: https://docs.ondoperps.xyz/funding-ondo-perps
Ondo Perps supports USDC and eligible Ondo tokenized stocks as collateral. USDC is supported on Ethereum and Arbitrum; Ondo tokenized stocks are currently supported on Ethereum only.
You can also fund an Ondo Perps Spot account with supported Ondo Private Markets Notes, but they are **not eligible as Perps collateral**. Check the supported asset and network in the funding flow. See [Private Markets](/private-markets).
**Tokenized Stock Collateral**
Eligible Ondo tokenized stocks are listed below. For readability, the interface displays a funded Ondo Stock's value as an equivalent balance under the corresponding asset symbol, and calculates its collateral value using a reference price. The displayed balance is a UI representation; the underlying tokenized stock remains the asset held by the user. The underlying tokens remain unchanged and are returned as the same tokenized stock amount on withdrawal, subject to changes in the total collateral amount caused by trading activity.
Haircuts and collateral caps below apply to collateral allocated to the user's Perps balance. A [haircut](/collateral-value) affects only how much of the displayed value contributes to margin; it does not reduce the quantity of underlying tokens. Withdrawal availability remains subject to margin requirements and other applicable restrictions.
See [Collateral Assets](/collateral-assets) for details.
| **Asset used to fund** | **Balance shown in UI** | **Haircut** | **Collateral cap (displayed equivalent quantity)** |
| :- | :- | :- | :- |
| **USDC** | USDC | 0% | None |
| **SPYon** | SPY equivalent | 10% | 675 |
| **QQQon** | QQQ equivalent | 10% | 700 |
| **NVDAon** | NVDA equivalent | 25% | 2,290 |
| **GOOGLon** | GOOGL equivalent | 15% | 1,485 |
| **TSLAon** | TSLA equivalent | 17% | 1,445 |
| **MUon** | MU equivalent | 15% | 520 |
| **SNDKon** | SNDK equivalent | 15% | 320 |
| **DRAMon** | DRAM equivalent | 15% | 8,735 |
| **SPCXon** | SPCX equivalent | 25% | 3,595 |
| **CRCLon** | CRCL equivalent | 25% | 6,350 |
| **SLVon** | SLV equivalent | 9% | 9,800 |
| **GLDon** | GLD equivalent | 6.5% | 1,360 |
The haircut is applied before the collateral cap. Each cap limits the equivalent quantity used to calculate margin after the haircut, rather than the quantity that can be funded. For example, with a 10% haircut and a cap of 675, a holding of 750 SPY equivalent reaches the cap: 750 × 90% = 675. Holdings above 750 SPY equivalent do not add margin under these settings.
The displayed equivalent quantity is the funded token quantity multiplied by the asset's applied S value. As S changes, the underlying token quantity needed to reach the cap changes. This calculation does not change the asset held.
Haircuts and collateral caps apply to each underlying asset and may change. Withdrawal availability remains subject to margin requirements and other applicable restrictions, including for holdings that do not add margin.
**Funding Addresses**
Ondo Perps does not require users to interact with a smart-contract bridge when funding. You can provision a unique funding address for Ondo Perps. Supported assets sent to this address are reflected in your Ondo Perps balance.
**Pricing**
The interface uses the applicable reference price to calculate the displayed equivalent balance and collateral value. A per-asset haircut is applied to reflect price volatility, transaction costs, and weekend gap risk. Haircuts vary by asset and change only how much value contributes to margin. For example, a 10% haircut means 90% of the reference value counts toward margin before applying the collateral cap; no tokenized stock is deducted.
# Funding Rates
Source: https://docs.ondoperps.xyz/funding-rates
## Overview
Funding rates keep perpetual contract prices aligned with the underlying oracle price. They are periodic payments between long and short holders: when the contract trades above the oracle price, longs pay shorts. When it trades below, shorts pay longs. This creates an economic incentive for traders to take positions that push the contract price back toward the oracle.
Funding on Ondo Perps is **purely peer-to-peer**. The protocol takes no cut. Every dollar debited from one side is credited to the other. The Insurance Fund is not involved in funding settlement.
## Funding Interval
Funding is paid **every hour**, 24 times per day. Intervals align to UTC hour boundaries (0:00, 1:00, 2:00, ... 23:00 UTC). The rate is applied on the first tick after each hour boundary.
## Scaling Factors
All perpetual markets share these defaults:
| **Parameter** | **Value** | **What it means** |
| :- | :- | :- |
| Funding interval | 1 hour | How often funding settles |
| Premium sampling | Every minute | 60 samples averaged per interval |
| Smoothing divisor | 8 | A sustained premium pays out over \~8 hours, not in one shock |
| Interest rate | 0.03%/day (\~10.95% APR) | Baseline cost of carry when premium is zero |
| Rate cap | +/-1%/hour | Hard ceiling, derived from margin safety constraint (see below) |
## Funding Rate Formula
The funding rate combines two components: the **average premium index** (how far the perp is from the oracle) and a fixed **interest rate** (the assumed cost of borrowing USDC to hold a position).
**Step 1: Average the premium**
The 60 per-minute premium index samples from the hour are averaged, then divided by 8:
```text theme={null}
Average Premium = mean(premium index samples) / 8
```
Dividing by 8 means only 1/8th of the premium is transferred per hourly interval. It takes 8 hours of sustained premium to transfer the full amount. This follows the industry-standard design of computing an 8-hour funding rate and paying it in hourly installments. This produces a smoother per-hour cash flow than venues that pay the full 8-hour rate every 8 hours.
**Step 2: Combine with interest rate**
```text theme={null}
Interest = 0.0003 / 24 = 0.0000125 per hour
Funding Rate = Average Premium + clamp(Interest - Average Premium, Interest - Damping Range)
```
The interest rate is fixed at **0.03% per day** (equivalent to 0.01% per 8 hours, or \~10.95% APR). This represents the assumed cost difference between holding the underlying asset (0% rate) and borrowing USDC.
The damping range creates a small dead zone: when the average premium is close to the interest rate, the funding rate collapses to the interest rate rather than oscillating.
**Step 3: Cap**
```text theme={null}
Funding Rate = clamp(Funding Rate, -1%, +1%)
```
The funding rate is capped at **1% per hour** in either direction.
This cap is not arbitrary. The platform enforces an invariant at configuration time:
```text theme={null}
fundingRateCap ≤ 0.75 × (initialMarginRate − maintenanceMarginRate)
```
The gap between initial margin and maintenance margin is the liquidation buffer, the cushion a max-leverage position has before liquidation triggers. The cap is set to at most 75% of that buffer, so even a worst-case funding hour only consumes three-quarters of the cushion. The remaining 25% gives you time to react (add margin, reduce size) before the next funding tick.
In practice, the configured 1% cap sits well below the constraint for every tier:
| **Tier** | **Initial Margin** | **Maintenance Margin** | **Buffer** | **0.75 x Buffer** | **Configured Cap** |
| :- | :- | :- | :- | :- | :- |
| 20x leverage | 5% | 2.5% | 2.5% | 1.875% | 1% |
| 10x leverage | 10% | 5% | 5% | 3.75% | 1% |
A single hour of max funding cannot, by itself, liquidate a margin-compliant position.
## Interest Rate Across Time Periods
| **Period** | **Interest Rate** |
| :- | :- |
| Per hour | 0.00125% |
| Per 8 hours | 0.01% |
| Per day | 0.03% |
| Annualized | \~10.95% APR |
When the perpetual contract trades exactly at the oracle price (zero premium), the funding rate equals the interest rate: longs pay shorts 0.00125% per hour. This reflects the cost of carry of a USDC-funded long position in the underlying.
## Funding Payment
Each hour, every open position receives or pays a funding fee:
```text theme={null}
Funding Payment = Position Size × Oracle Price × Funding Rate
```
The oracle price (not the exchange mid-price) is used to convert position size to notional value. If the funding rate is positive, longs are debited and shorts are credited. If the funding rate is negative, the reverse applies.
The payment is credited to or debited from your USDC balance and reflected immediately in your margin balance.
## Worked Example
TSLA is trading at a premium to the oracle. You are **long 100 TSLA** with an oracle price of **\$250**.
**Premium index calculation:**
The orderbook impact prices for a \$1,000 simulated order are:
* Impact Bid = \$251.50
* Impact Ask = \$252.00
* Oracle Price = \$250.00
```text theme={null}
Premium Index = (max($251.50 - $250, 0) - max($250 - $252, 0)) / $250
= ($1.50 - $0) / $250
= 0.006 (0.6%)
```
This sample is positive, meaning the perp is trading above the oracle.
**Funding rate calculation:**
Assume the average premium index over the hour is 0.48% (other samples were lower). Divide by 8:
```text theme={null}
Average Premium = 0.0048 / 8 = 0.0006
```
Combine with the hourly interest rate:
```text theme={null}
Interest = 0.0000125
Funding Rate ≈ 0.0006 + clamp(0.0000125 - 0.0006, -0.0000625, +0.0000625)
= 0.0006 + (-0.0000625)
= 0.0005375 (0.05375%)
```
This is within the +/-1% cap, so no clamping.
**Funding payment:**
```text theme={null}
Funding Payment = 100 × $250 × 0.0005375 = $13.44
```
As a long holder with a positive funding rate, you **pay \$13.44** this hour. This amount is debited from your USDC balance. Short holders of equivalent size collectively receive the same amount.
## More Examples
All examples assume production scaling factors and a long \$1,000 notional position.
**Market at rest (zero premium)**
The perp trades right on the oracle for an hour, so the average premium is zero. Zero falls inside the +/-0.00625% damping band around the 0.00125%/hr interest rate, so damping fires and the funding rate equals the interest rate:
```text theme={null}
Funding Rate = 0.00125%/hr
Payment = 0.0000125 × $1,000 = $0.0125/hr
```
Longs pay shorts about a cent per hour per \$1,000 of notional. This is the "cost of carry" baseline.
**Extreme premium hitting the cap (10% sustained)**
The perp trades 10% above the oracle for a full hour. The premium component is 10% / 8 = 1.25%/hr, well above the 1% cap:
```text theme={null}
Uncapped Rate ≈ 1.245%/hr
Funding Rate = clamp(1.245%, -1%, +1%) = 1%/hr
Payment = 0.01 × $1,000 = $10/hr
```
This is the worst case. Even so, because the 1% cap sits below the maintenance margin buffer (see Step 3 above), this cannot liquidate a margin-compliant position in a single hour.
## Data Quality
The funding rate depends on 60 premium samples per hour. If too many samples are missing, the rate could be distorted. To guard against this, the platform checks for gaps between consecutive samples: if the largest gap within the hour exceeds **6 minutes**, funding for that hour is set to zero. No payment is exchanged, and the system logs a warning.
This means a brief oracle interruption (under 6 minutes) is tolerated, but a sustained data gap causes funding to pause rather than publish a rate based on incomplete information.
## Closed Underlying Market
When the underlying market is closed and there is no external oracle price, premium samples for those minutes are calculated using an internal price derived from the exchange's orderbook data. Funding rates are still paid but can be much larger as a consequence of the price discovery mechanism.
## Learn More
The premium-index formula, impact prices, and how mark-vs-book is measured.
Where the mark price comes from and how it's aggregated.
Maker, taker, liquidation fees, and how funding differs from a fee.
The full liquidation waterfall and how the funding-rate cap interacts with maintenance margin.
# Key Trading Concepts
Source: https://docs.ondoperps.xyz/key-trading-concepts
Familiarize yourself with the core terms used across the platform before placing your first trade.
| **Term** | **Definition** |
| :- | :- |
| Entry Price | The price at which a position is opened, excluding fees. A long position is profitable above this price and at a loss below it. |
| Mark Price (perpetual) | A fair-value price derived from multiple major exchanges. Used for PnL calculations, liquidations, funding, and margin calculations. |
| Open Interest (perpetual) | The total notional value of all open positions in a given market. |
| Funding Rate (perpetual) | Funding payments ensure perpetual prices track the underlying asset. Funding occurs hourly (UTC), traders either pay or receive funding depending on their position and market conditions. The funding rate reflects the basis between the perpetual and underlying market. Corporate actions such as dividends are accounted for through platform pricing mechanisms. |
| Net Position | Your total asset exposure across all positions. |
| Net Leverage (perpetual) | Actual leverage across all open positions. This may differ from the leverage set at entry due to trading fees, reduced buying power as positions are opened, and margin safety buffers. |
| Liquidation Price (perpetual) | The estimated price at which a position is automatically closed to prevent negative equity. |
| uPnL (Unrealized PnL, perpetual) | The potential profit or loss on an open position if closed at the current mark price. |
| Take Profit & Stop Loss (TP/SL, perpetual) | Automated exit orders triggered by the mark price. Reduce-only and operate as OCO (one cancels the other), when one is triggered, the other is automatically cancelled. |
| Order | An instruction to buy or sell. An order can rest on the book, fill in part, or be cancelled. |
| Fill | An executed trade. One order can produce several fills at different prices. |
| Open order | An order resting on the book with an unfilled quantity. Its unfilled portion holds the funds or the asset needed to complete it. |
| Reservation | The amount an open order or a pending withdrawal holds. Reserved amounts cannot fund another action. Cancelling an order releases only that order's reservation. |
| Spot balance | The user's displayed balance for assets available for spot trading or held following a spot purchase. For tokenized stocks, it shows an equivalent quantity of the underlying GM token. |
| Perps balance | The user's displayed balance for collateral allocated to support perpetual positions. For tokenized stocks, the underlying GM token remains the collateral asset. |
# Leverage
Source: https://docs.ondoperps.xyz/leverage
## Cross Margin
Ondo Perps uses cross margin. All your positions share a single collateral pool, meaning your entire margin balance supports every open position across every market.
There is no isolated margin mode. This is a design choice: cross margin maximizes capital efficiency by letting gains in one position offset losses in another. You do not need to allocate margin to individual positions or move funds between them.
The trade-off is that a large loss in one perpetual position can affect the user's Perps margin health. Liquidation risk is based on overall Perps margin health, not any single position.
***
## Leverage
Leverage determines how much margin is required to open a position. Higher leverage means less margin required, but a tighter distance to liquidation.
You can set leverage per market to any integer from 1x up to the market maximum. The maximum depends on the asset:
| **Asset Class** | **Markets** | **Max Leverage** | **Initial Margin Rate** | **Maintenance Margin Rate** |
| :- | :- | :- | :- | :- |
| Indices | US100, US500 | 20x | 5.0% | 2.5% |
| Commodities | XAG (Silver), XAU (Gold), WTI (Oil) | 20x | 5.0% | 2.5% |
| Commodities | BRENT (Oil) | 15x | 6.67% | 3.33% |
| ETFs | DRAM, EWY | 10x | 10.0% | 5.0% |
| Equities | AAPL | 20x | 5.0% | 2.5% |
| Equities | AMD, AMZN, COIN, CRCL, GOOGL, HOOD, INTC, META, MRVL, MSFT, MSTR, MU, NFLX, NVDA, ORCL, PLTR, SPCX, TSLA | 10x | 10.0% | 5.0% |
If you set leverage lower than the market maximum, your initial margin rate increases accordingly. For example, setting AAPL to 10x means your initial margin rate is 10% instead of 5%.
Lowering leverage on an existing position requires enough available margin to cover the higher margin requirement. You cannot lower leverage if it would put the user's Perps margin balance below the initial margin threshold.
***
## Initial Margin
Initial margin is the collateral required to open a position:
`initial_margin = (price x quantity) / leverage`
This is the same as: `initial_margin = notional_position_value x initial_margin_rate`
Where `initial_margin_rate = 1 / leverage`.
**Example:** Opening a \$10,000 position in AAPL at 20x leverage requires \$500 in initial margin (\$10,000 / 20).
When you have multiple positions and resting orders, your total used margin is calculated per market as:
`used_margin = max(buy_side_margin, sell_side_margin) + open_losses`
Buy and sell positions partially offset each other, so your used margin is the larger of the two sides, not the sum. The user's used margin is the total across perpetual markets.
***
## Maintenance Margin
Maintenance margin is the minimum collateral required to keep a position open. If your margin balance falls to the maintenance margin level, your position enters liquidation.
`maintenance_margin = notional_position_value x maintenance_margin_rate - maintenance_amount`
The maintenance margin rate is always half the initial margin rate at maximum leverage:
* 20x markets: 2.5% maintenance margin rate
* 10x markets: 5.0% maintenance margin rate
### Margin Tiers
Ondo Perps supports tiered margin brackets. As position size grows, higher tiers may apply with different margin rates. The `maintenance_amount` in the formula is a smoothing coefficient that prevents abrupt jumps at tier boundaries, ensuring your maintenance margin changes continuously as your position size changes.
Currently, each market has a single tier with a \$1,000,000 position bracket. For most traders, this means a flat maintenance margin rate applies to your entire position.
**Example:** A \$10,000 AAPL position at 20x leverage has a maintenance margin of \$250 (\$10,000 x 2.5%).
***
## Margin Balance and Margin Ratio
### Margin Balance
The user's Perps margin balance is calculated as follows:
`margin_balance = wallet_balance + unrealized_pnl`
Where wallet balance includes funding, realized PnL, and funding payments.
### Available Margin
Available margin is what you can use to open new positions or withdraw:
`available_margin = margin_balance - total_initial_margin`
### Margin Ratio
Margin ratio shows how close the user's Perps balance is to liquidation:
`margin_ratio = maintenance_margin / margin_balance`
* **0%**: No open positions or fully collateralized
* **Below 100%**: Account is healthy
* **100%**: Liquidation is triggered
***
Accepted collateral, haircuts and caps are listed on [Funding Ondo Perps](/funding-ondo-perps).
# Order price protection
Source: https://docs.ondoperps.xyz/mark-price-protection
Order price protection helps prevent buys at excessively high prices and sells at excessively low prices.
## Reference prices
Spot orders are checked against an external reference price for the underlying asset. Trades execute at prices available in the Spot order book; the reference price is used to check whether an order is acceptable.
Perpetual orders are checked against the market's mark price. Additional price limits can apply during [weekend and extended hours trading](/weekend-trading).
## How protection affects your order
The permitted price range can vary by market. A limit buy priced too high or a limit sell priced too low can be rejected. Market orders are checked against available liquidity and can be rejected if the requested quantity cannot be filled within the permitted range.
An immediate-or-cancel (IOC) limit order lets you set your own execution-price limit. It may fill partially and cancel the remainder, subject to the market's price-protection rules.
Passing these checks does not guarantee a fill. Review your order status and completed fills before placing a replacement. See [Order types](/order-types) for order behavior.
For API fields and validation rules, see the [Spot API guide](/api-reference/spot-api-guide#price-protection-rules).
# Markets
Source: https://docs.ondoperps.xyz/markets
## Supported Markets
Ondo Perps lists two kinds of market. Perpetual markets are USD-settled contracts denoted by the `.P` suffix, for example `SPY-USD.P`. Spot markets include Ondo Global Markets tokens, such as SPYon in `SPY/USDC`, and supported Ondo Private Markets Notes in the **Private Markets** category. For Global Markets tokens, the Spot symbol identifies the market and the interface shows an equivalent balance under the corresponding asset symbol. This is a display representation only; the underlying tokenized stock remains the asset you hold and is returned to your wallet when withdrawn, subject to applicable withdrawal requirements. For Private Markets mechanics, see [Private Markets](/private-markets).
Markets ordinarily support trading 24 hours a day, 7 days a week, including when the underlying market is closed. Individual markets can pause for corporate actions or other trading restrictions. For Global Markets pricing outside of U.S. market hours, see [Weekend and extended hours trading](/weekend-trading).
Default maximum position size on perpetual markets is \$1,000,000 per market, per account.
### Equity Perpetuals
| **Market** | **Asset** | **Max Leverage** |
| :- | :- | :- |
| AAPL-USD.P | Apple | 20x |
| AMD-USD.P | AMD | 10x |
| AMZN-USD.P | Amazon | 10x |
| COIN-USD.P | Coinbase | 10x |
| CRCL-USD.P | Circle | 10x |
| GOOGL-USD.P | Alphabet (Google) | 10x |
| HOOD-USD.P | Robinhood | 10x |
| INTC-USD.P | Intel | 10x |
| META-USD.P | Meta | 10x |
| MRVL-USD.P | Marvell Technology | 10x |
| MSFT-USD.P | Microsoft | 10x |
| MSTR-USD.P | MicroStrategy | 10x |
| MU-USD.P | Micron Technology | 10x |
| NFLX-USD.P | Netflix | 10x |
| NVDA-USD.P | NVIDIA | 10x |
| ORCL-USD.P | Oracle | 10x |
| PLTR-USD.P | Palantir | 10x |
| SPCX-USD.P | SpaceX | 10x |
| TSLA-USD.P | Tesla | 10x |
### Index Perpetuals
| **Market** | **Asset** | **Max Leverage** |
| :- | :- | :- |
| US500-USD.P | S\&P 500 | 20x |
| US100-USD.P | Nasdaq 100 | 20x |
### Commodity Perpetuals
| **Market** | **Asset** | **Max Leverage** |
| :- | :- | :- |
| XAU-USD.P | Gold | 20x |
| XAG-USD.P | Silver | 20x |
| WTI-USD.P | Crude Oil (WTI) | 20x |
| BRENT-USD.P | Crude Oil (Brent) | 15x |
### ETF Perpetuals
| **Market** | **Asset** | **Max Leverage** |
| :- | :- | :- |
| DRAM-USD.P | Roundhill Memory ETF | 10x |
| EWY-USD.P | iShares MSCI South Korea ETF | 10x |
### Spot markets
| Market | Underlying tokenized asset |
| :- | :- |
| SPY/USDC | SPDR S\&P 500 ETF token (SPYon) |
| QQQ/USDC | Invesco QQQ ETF token (QQQon) |
| GLD/USDC | SPDR Gold Shares token (GLDon) |
| SLV/USDC | iShares Silver Trust token (SLVon) |
| CRCL/USDC | Circle tokenized stock (CRCLon) |
| SPCX/USDC | SpaceX tokenized stock (SPCXon) |
| SNDK/USDC | Sandisk tokenized stock (SNDKon) |
| TSLA/USDC | Tesla tokenized stock (TSLAon) |
| NVDA/USDC | NVIDIA tokenized stock (NVDAon) |
| GOOGL/USDC | Alphabet tokenized stock (GOOGLon) |
| MU/USDC | Micron tokenized stock (MUon) |
| DRAM/USDC | Roundhill Memory ETF token (DRAMon) |
Minimum order quantities and price increments vary by market. See [Spot trading](/spot-trading) for order requirements.
Spot markets are fully funded and do not offer leverage. The markets available to a given customer can differ. See [Spot trading](/spot-trading).
### Private Markets
Private Markets notes trade as fully funded spot assets in their own category.
| Token | Product | Perps collateral |
| :- | :- | :- |
| OPAIPon | Ondo Private Markets Note | Not eligible |
OPAIPon has its own [trading fees](/fees#private-markets-trading-fees) and supports funding and withdrawals through the normal flows, subject to supported assets, networks and applicable restrictions. Private Markets trading is available 24/7, subject to platform maintenance, risk controls and any pause by the Issuer. See [Private Markets](/private-markets) for exchange mechanics and [Private Markets concepts and terms](https://docs.ondo.finance/private-markets) for eligibility, redemption and risks.
## Fees
See [Fees](/fees) for perpetual trading rates, Global Markets Spot fees and the separate [Private Markets trading fee schedule](/fees#private-markets-trading-fees).
## Perpetual funding
| **Parameter** | **Value** |
| :- | :- |
| Daily Interest Rate | 0.03% |
| Funding Rate Cap | 1% per interval |
| Funding Intervals | 24 per day |
## Underlying Market Holiday Schedule (2026-2027)
Ondo Perps markets ordinarily support trading 24/7, subject to market pauses and trading restrictions. The underlying public markets will be closed on the following dates. Private Markets trading is not tied to U.S. stock-market hours. For details on Global Markets pricing outside of U.S. market hours, see [Weekend and extended hours trading](/weekend-trading).
| **Date** | **Holiday** | **Status** |
| :- | :- | :- |
| 2026-04-03 | Good Friday | Closed |
| 2026-05-25 | Memorial Day | Closed |
| 2026-06-19 | Juneteenth | Closed |
| 2026-07-03 | Independence Day (observed) | Closed |
| 2026-09-07 | Labor Day | Closed |
| 2026-11-26 | Thanksgiving | Closed |
| 2026-11-27 | Day after Thanksgiving | Early close (1:00 PM ET) |
| 2026-12-24 | Christmas Eve | Early close (1:00 PM ET) |
| 2026-12-25 | Christmas | Closed |
| 2027-01-01 | New Year's Day | Closed |
# Order Types
Source: https://docs.ondoperps.xyz/order-types
Ondo Perps supports a full suite of order types designed for both straightforward execution and sophisticated trading strategies. Market and limit orders are available on both perpetual and spot markets. TP/SL and TWAP apply to perpetual markets only.
* **Market Orders**: Execute immediately at the best available price.
* **Limit Orders**: Execute at a specified price or better, resting on the order book until filled or cancelled.
* **Take Profit / Stop Loss (TP/SL)**: Automatically close a position when the mark price reaches a specified level, on either the upside or downside.
* **TWAP**: Divide a large order into smaller child orders executed at intervals over a set duration, reducing market impact.
Reduce-only orders are not supported on spot markets.
## Trading against your own orders
Orders placed through the same account cannot match against one another.
Where a new order would cross resting orders placed through the same account, those resting orders are cancelled and the new order proceeds against the rest of the book. The cancellation is recorded with a self-match prevention reason and can be identified in order history.
Users quoting on both sides of a market should review their resting orders before placing an order that could cross them. An aggressive order on one side cancels resting orders from the same account that it would cross on the other side.
# Overview
Source: https://docs.ondoperps.xyz/overview
Ondo Tokenized Stock Collateral lets you use your tokenized equities as margin for perpetual futures trading on Ondo Perps, without selling them.
Eligible Ondo tokenized stocks can fund perpetual trading without first being sold for USDC. Their collateral value contributes to the user's available margin, subject to applicable reference prices, haircuts and caps.
The interface shows the user's funded assets and USDC in two separate balances. The user's Perps balance reflects collateral allocated to support perpetual positions. The user's Spot balance reflects assets available for spot trading or held following a spot purchase. For tokenized stocks, each equivalent balance is a display representation of the underlying tokenized stock. See [Balances](/balances).
**Supported assets:** fund with an eligible Ondo tokenized stock on Ethereum. The interface displays an equivalent balance under the corresponding asset symbol. The underlying tokenized stock remains the asset held by the user. See [Funding Ondo Perps](/funding-ondo-perps) and [Collateral Assets](/collateral-assets) for details.
## **Benefits of Tokenized Stock Collateral**
* **Capital efficiency:** Put your idle tokenized equities to work as margin instead of leaving them sitting idle in your wallet.
* **Maintain equity exposure:** Trade perps without selling your equity positions. Underlying tokenized stocks remain the user's collateral assets unless sold through Auto-Exchange or otherwise traded or withdrawn by the user.
* **Access leverage:** Your tokenized equities contribute to your available margin, giving you the ability to open leveraged perpetual positions.
* **Real-time portfolio visibility:** The interface shows the user's total portfolio value, each asset's contribution to Perps margin, and overall Perps margin health.
## **How It Works**
Fund with an eligible tokenized stock to make it available as Perps collateral. Its contribution to margin is calculated using the reference price and applicable haircut, subject to the collateral cap. The interface displays an equivalent balance; the underlying tokenized stock remains unchanged. See [Funding Ondo Perps](/funding-ondo-perps) and [Collateral Value](/collateral-value) for the current rules.
Opening a perpetual position does not itself sell the underlying tokenized stock used as collateral. Its value changes with the applicable reference price. A fall in collateral value can reduce the user's margin even when an open position is profitable. Auto-Exchange can sell eligible collateral to cover USDC Debt. Liquidation closes perpetual positions and does not directly sell collateral tokens.
Funding with an eligible tokenized stock makes it available as collateral in the user's Perps balance. The underlying GM token remains the collateral asset. You retain beneficial ownership and may withdraw subject to margin health requirements. The exchange has the right to convert collateral allocated to the user's Perps balance to USDC in the event of Auto-Exchange or liquidation. Assets allocated to the user's Spot balance are not converted by those processes.
# Perpetual Futures
Source: https://docs.ondoperps.xyz/perpetual-futures
Perpetual futures provide continuous, leveraged exposure to an underlying market, such as stocks or commodities. Unlike traditional futures, perpetuals have no expiry date and require no rolling; positions can be held indefinitely with sufficient margin, with funding rates keeping perp prices anchored to the underlying asset.
Ondo Perps offers perpetual futures on the world's most actively traded U.S. equities, indices, ETFs, and commodities. Trading is available 24/7, including outside traditional market hours and on weekends.
Perpetual futures give leveraged exposure without ownership of the underlying asset. Spot markets support fully funded purchases of the corresponding Ondo Global Markets token, with no leverage or perpetual funding payments. The interface shows the purchased tokenized stock as an equivalent balance. See [Spot trading](/spot-trading).
Key characteristics:
* No expiry date
* Long and short positions supported
* Funding rates are designed to keep perp prices anchored to the underlying asset
* Positions can be held indefinitely with sufficient margin
# Placing Your First Order
Source: https://docs.ondoperps.xyz/placing-your-first-order
Ondo Perps uses a standard perpetual futures order interface. The following walks through each step from market selection to order submission.
**1. Select a Market**
Use the market selector at the top left to choose the asset you want to trade. Each market displays the current mark price, 24h change, 24h volume, open interest, and funding rate.
**2. Choose a Side**
Select **Long** to open a position that profits if the asset price rises, or **Short** to open a position that profits if the asset price falls.
**3. Select an Order Type**
* **Market**: Executes immediately at the best available price
* **Limit**: Executes at your specified price or better; rests in the order book until filled or cancelled
* **TWAP**: Splits a large order into smaller pieces executed over a defined time window, reducing market impact
**4. Set Leverage**
Tap the leverage control (e.g. "Cross 10x") in the top right of the order panel to adjust your leverage. Ondo Perps currently supports cross margin mode, meaning the user's Perps margin balance supports open perpetual positions; assets allocated to Spot do not. Higher leverage increases both potential returns and liquidation risk.
**5. Enter an Amount**
Enter your order size in USD notional value. To switch to asset units (e.g. number of NVDA shares), tap the toggle icon next to the amount field.
**6. Configure Optional Parameters**
* **Reduce Only**: Restricts the order to reducing an existing position; it will not open or increase a position
* **Take Profit / Stop Loss**: Set a target price to automatically close your position at a gain (TP) or limit your loss (SL)
**7. Review the Order Summary**
Before submitting, review the order details displayed below the entry form:
* **Order Value**: Total notional value of the position
* **Est. Price**: Estimated fill price
* **Net Position**: Your resulting position after the order fills
* **Est. Liq. Price:** The mark price at which your position would be liquidated
* **Fee:** Estimated trading fee for the order; see [Fees](/fees)
* **Est. Slippage:** Estimated price impact for market orders
**8. Place the Order**
Once you've reviewed the summary, click **Enter Order Amount** to confirm and submit. Filled orders will appear under the **Fills** tab at the bottom of the screen. Open positions are tracked under **Positions**.
**First Perpetual Trade Walkthrough**
1. Fund USDC via the **Fund** button in the top right corner
2. Navigate to the market you want to trade using the market selector
3. Select **Long** or **Short**
4. Set your leverage using the leverage control
5. Enter your order amount in USD or asset units
6. Select your order type: Market for immediate execution, Limit to specify a price, or TWAP to execute over a defined window
7. Optionally set a Take Profit or Stop Loss
8. Review the estimated liquidation price, fee, and slippage
9. Click **Enter Order Amount** to submit
**First Spot Trade**
Fund Ondo Perps, transfer an available amount to the user's Spot balance, select a spot market, and place a market or limit order. The purchase is of the corresponding GM token; the interface shows a displayed equivalent quantity. See [Spot trading](/spot-trading).
# Positions, Balances, and Leverage
Source: https://docs.ondoperps.xyz/positions-balances
Perpetual trading on Ondo Perps is cross-margined: the user's full Perps margin balance backs that user's open perpetual positions together. Spot trading is fully funded and is not margined. For tokenized-stock collateral, the interface displays an equivalent balance while the underlying GM token remains the collateral asset. See [Balances](/balances).
| Term | Definition |
| :- | :- |
| Wallet Balance | Reflects funding and withdrawals, and transfers between the Spot and Perps balances, realized PnL, funding fee payments and receipts, and exchange fees. |
| Margin Balance | The sum of Wallet Balance and Unrealized PnL. Used to calculate leverage and margin ratio. |
| Used Margin | The balance reserved as collateral for open positions and resting orders, summed across all markets. Calculated as `max(long used margin, short used margin)` per market. |
| Available Margin | The balance available for placing new orders. Equal to Margin Balance minus Used Margin. |
| Withdrawable Margin | The balance available for withdrawal, after margin requirements and excluding unrealized profit. See Withdrawals for the calculation. |
# Positions Panel
Source: https://docs.ondoperps.xyz/positions-panel
## Trading activity
The bottom panel of the trade page shows the user's trading activity across the tabs below.
### Positions
Displays all currently open positions. Each row shows:
| **Field** | **Description** |
| :- | :- |
| Contract | Market and direction (e.g. Short 20x) |
| Position | Long or short |
| Size | Quantity held |
| Value | Current notional value in USD |
| Entry Price | Average price at which the position was opened |
| Mark Price | Current mark price |
| Est. Liq. Price | Estimated liquidation price |
| uPnL | Unrealized profit or loss |
| TP/SL | Take profit and stop loss levels, if set |
| Net Funding | Cumulative funding payments received or paid |
| Collateral | Collateral value backing the position, shown under the corresponding asset symbol (e.g. USDC, SPY, QQQ) |
To manage a position, use the action controls on the right. **Market Close** immediately closes the position at the current market price. **Manage** opens a dropdown with: Take Profit / Stop Loss, Market Close, Limit Close, View Position Details, and Share PnL. To close all open positions at once, use the **Close All Positions** button at the top right of the panel.
***
### Orders
Displays order history with **All**, **Open**, and **Filled** filter tabs. Each row shows the market, side, order date, order type, order price, filled amount, and status. Cancel individual open orders via **Cancel Order**, or all at once via **Cancel All**. The **Manage** dropdown on each row offers options to view order details or cancel the order.
An order records the instruction; a fill records an executed trade. One order can produce several fills, and a partially filled order can later be cancelled. Cancelling an order releases the funds or asset reserved for the unfilled portion only; it does not reverse fills that have already occurred. An acknowledgement of a cancellation request is not confirmation of the final outcome.
***
### Fills
A chronological record of all executed fills. Each row shows the market, direction (e.g. Open Short, Close Long), fill date, type (Taker/Maker), fill price, fill amount, fill cost, fee, and closed PnL where applicable.
Reconciling a trade means comparing the executed quantity, the fill prices, the fee charged and the net amount received against the resulting balance. Order history and fill history can each be exported.
***
### Funding History
A record of all funding payments applied to your positions. Each row shows the market, date, position size, mark price at time of payment, payment amount, and funding rate.
***
### TWAP
Displays active and historical TWAP orders. Active orders show the market, side, start time, running time, filled/order size, average price, reduce only status, and current status.
***
### Fundings
A record of funding, showing the underlying tokenized stock sent, such as SPYon, its token quantity, date and status, and the displayed equivalent balance, such as SPY equivalent.
***
### Withdrawals
A record of withdrawals, showing the displayed equivalent quantity requested, date and status, and the underlying tokenized stock and token quantity sent to the user's external wallet.
# Premium Index
Source: https://docs.ondoperps.xyz/premium-index
The premium index measures how far the perpetual contract price has diverged from the oracle price. This divergence drives the funding rate: when the perp trades at a premium, longs pay shorts, incentivizing the price back toward the oracle. When it trades at a discount, shorts pay longs. The premium index is the mechanism that keeps perpetual futures prices anchored to their underlying assets.
The formula:
```text theme={null}
Premium Index = (max(Impact Bid - Oracle Price, 0) - max(Oracle Price - Impact Ask, 0)) / Oracle Price
```
## Impact Prices
The impact bid and ask prices are the volume-weighted average execution prices for a simulated order of a standard size against the order book:
* **Impact Bid Price:** Average fill price for a simulated sell order of the Impact Price Quote Size
* **Impact Ask Price:** Average fill price for a simulated buy order of the Impact Price Quote Size
## Impact Price Quote Size
The Impact Price Quote Size is **\$1,000 (notional) for all perpetual markets**. There is no per-market differentiation. This applies uniformly across all perpetual markets (equities, indices, commodities, and ETFs).
## Interpretation
| **Condition** | **Premium Index** |
| :- | :- |
| Oracle price within the bid-ask spread | Zero |
| Perp trading above oracle (premium) | Positive |
| Perp trading below oracle (discount) | Negative |
The premium index is sampled every minute (60 samples per funding interval) and averaged over the hour to compute the funding rate.
## Learn More
How the premium index combines with the interest rate to produce the hourly funding rate.
The external oracle price the premium index is measured against.
# External Pricing Derivations
Source: https://docs.ondoperps.xyz/pricing-derivations
The oracle price for every market on Ondo Perps is sourced from one or more independent external price feeds. Perpetual markets use it to derive the mark price. Spot markets execute at order-book prices and use the oracle price for the underlying to check that an incoming order's price is reasonable; execution prices come from the book.
## Equity and ETF perpetuals
All equity and ETF perpetuals are priced using **direct spot prices** from two independent oracle sources, **Pyth** and **Stork**. Both provide continuous 24/5 pricing data for regular hour, post-market, overnight, and pre-market prices.
When both sources respond, the external price is the **average of Pyth and Stork**. If only one source responds, that price is used directly.
## Index perpetuals (US100, US500)
Indices do not have a direct spot market. Index perpetuals use Pyth futures feeds and convert futures prices to synthetic spot prices using the standard cost-of-carry relationship. This accounts for the risk-free rate, convenience yield, and time to expiration.
## Commodity perpetuals
Commodity perpetuals are priced differently depending on whether a liquid spot market exists for the underlying asset.
**Precious metals (Gold, Silver)** have deep, liquid spot markets, so their external price is sourced directly from spot price feeds.
**Energy commodities (WTI)** do not have spot markets as other assets do — the underlying price varies based on a range of factors, including delivery timing and geography. To derive an external price, the oracle uses an industry-standard approach that references a fixed set of designated futures contracts.
The underlying contract is rolled from the 5th to the 10th business day of each month, at predefined timestamps that correspond to internal pricing sessions. Over this window the external price transitions from the front (near) contract to the next contract:
| Business day | Front contract | Next contract |
| :- | :- | :- |
| 5th | 100% | 0% |
| 6th | 80% | 20% |
| 7th | 60% | 40% |
| 8th | 40% | 60% |
| 9th | 20% | 80% |
| 10th | 0% | 100% |
The roll schedule respects the CME holiday calendar.
## Learn more
Why the platform uses an external oracle aggregate rather than the local last-traded price.
How the oracle price combines with local impact prices to form the premium index.
# Private Markets
Source: https://docs.ondoperps.xyz/private-markets
Trade OPAIPon on Ondo Perps: Private Markets spot mechanics, fees, collateral eligibility, funding and withdrawals.
Private Markets is a category of spot markets on Ondo Perps where eligible users can buy and sell supported Ondo Private Markets Notes. Each note is a tokenized debt obligation of PM Issuer Co (BVI) Limited, designed to provide economic exposure to the performance of a specified private company.
For what you own, eligibility, redemption and risks, read [Private Markets concepts and terms](https://docs.ondo.finance/private-markets).
Notes are unsecured obligations of the Issuer, not shares in the Reference Company. They provide no shareholder rights and are not eligible as collateral for perpetual positions on Ondo Perps. Notes are not shares, nor do the notes grant any shareholder or participation rights in any Reference Company. Notes are not associated with any Reference Company unless expressly stated in such Note’s offering documents.
## OPAIPon
**OPAIPon** is the Ondo Private Markets Note listed on Ondo Perps in the **Private Markets** category. You can buy and sell OPAIPon as a fully funded spot asset, fund an Ondo Perps Spot account with supported notes and withdraw available notes to your wallet. The interface shows your note holdings as a displayed Spot balance. OPAIPon has its own trading fees and is **not eligible as Perps collateral**.
## Trading mechanics
| Feature | Private Markets on Ondo Perps |
| :- | :- |
| Market type | Fully funded spot trading |
| Category | Private Markets |
| Asset traded | OPAIPon, an Ondo Private Markets Note |
| Leverage and borrowing | None |
| Perpetual funding payments | None |
| Fees | A separate [Private Markets trading fee schedule](/fees#private-markets-trading-fees) |
| Perps collateral eligibility | Not eligible |
| Funding and withdrawals | Supported through the normal funding and withdrawal flows, subject to asset and network support and applicable restrictions |
| Trading hours | 24/7, subject to platform maintenance, risk controls and any pause by the Issuer |
A buy purchases the note. A sell uses notes you hold; it does not open a short position. Orders may fill partly or not at all, depending on available liquidity.
## Prices and liquidity
Buyers and sellers set prices on the spot order book. There is no public stock-market price or consensus benchmark price for the note, nor is there a publicly available price for the Reference Company while it remains private.
The trading price can differ significantly from the company's latest private valuation and from the note's eventual redemption payout. Liquidity may be limited, spreads may be wide, and trading availability does not guarantee that you can sell when you want to. Liquidity and pricing are entirely dependent on market participants.
Trading is not tied to U.S. stock-market hours.
## Fees
Private Markets has its own trading fees. Do not assume that the fee rates for perpetual markets or other spot markets apply. Introductory fees apply. Fees are subject to change and may differ for future listings.
| Maker | Taker |
| :- | :- |
| 25 bps (0.25%) | 50 bps (0.50%) |
See the [Private Markets trading fee schedule](/fees#private-markets-trading-fees).
Trading fees are separate from any tax withholding or settlement fees deducted from an eventual redemption payout by the Issuer.
## Holding notes and Perps collateral
Your Private Markets holdings are spot assets. They do not contribute margin, increase your perpetual buying power or back your perpetual positions.
Funding an Ondo Perps Spot Account with a note or buying one does not make it eligible as Perps collateral. See [Balances](/balances) and [Collateral Assets](/collateral-assets) for the distinction between spot holdings and eligible collateral.
## Funding and withdrawals
You can fund an Ondo Perps Spot Account with supported notes and withdraw available notes through the normal funding and withdrawal flows. Check the supported asset and network before funding or requesting a withdrawal.
A note withdrawal sends the token to your external wallet. It does not sell the note, redeem it with the Issuer or deliver shares in the Reference Company. Open orders, pending withdrawals, platform restrictions and the Issuer's transfer controls can affect availability.
Holding or receiving a note remains subject to the product's eligibility and transfer restrictions. See [Funding Ondo Perps](/funding-ondo-perps) and [Withdrawals](/withdrawals) for general wallet funding and withdrawal mechanics.
## Selling and redemption are different
Selling on Ondo Perps is a secondary-market trade at the available market price. Redemption is the Issuer's separate process following a Qualifying Liquidity Event, with a payout calculated under the note's Token Terms.
Holding notes on Ondo Perps does not automatically complete the Issuer's claim requirements. Eligible holders must make a valid claim within the applicable claim window. Review the note's Token Terms for redemption and claim requirements.
## Eligibility and governing terms
Ondo Private Markets is available to qualifying non-U.S. persons only and is subject to other jurisdictional restrictions. You must meet both the platform's access requirements and the note's eligibility requirements.
For more information about the underlying note, visit [Ondo Private Markets](https://docs.ondo.finance/private-markets). Review the note’s Offering Documents, Token Terms and Risk Factors for its full legal and economic terms.
This page is provided for informational purposes only and does not constitute investment advice, an offer or a solicitation. Each note is governed by its own Offering Documents, Token Terms and Risk Factors. In the event of any conflict, the Token Terms control.
# Profit & Loss
Source: https://docs.ondoperps.xyz/profit-and-loss
## PnL Calculation
**Entry Price** When opening or increasing a position, the entry price is calculated as a size-weighted average of all opening trades:
`Entry Price = (Previous Entry Price x Previous Size + Trade Price x Trade Size) / New Position Size`
When partially or fully closing a position, the entry price remains unchanged.
**Unrealized PnL** The potential profit or loss on an open position if closed at the current mark price:
`Unrealized PnL = (Mark Price - Entry Price) x Position Size`
For long positions, uPnL is positive when mark price is above entry price. For short positions, uPnL is positive when mark price is below entry price.
**Realized PnL** Captured when closing a position, in full or in part:
`Realized PnL = (Exit Price - Entry Price) x Closed Size`
Funding payments are applied to realized PnL at each hourly settlement. Trading fees are deducted separately.
**Total PnL** `Total PnL = Realized PnL + Unrealized PnL`
*Example: A trader opens a long NVDA-USD.P position of 10 shares at \$875.00. If the mark price moves to \$900.00, uPnL = (900 - 875) x 10 = \$250.00. If the trader closes the full position at \$900.00, realized PnL = \$250.00 minus applicable fees.*
# Public Beta
Source: https://docs.ondoperps.xyz/public-beta
Ondo Perps is now live in Public Beta. Qualifying users can now access the platform directly via the app. During the Public Beta, certain features and functionality may be subject to restrictions, limits, or changes.
Reduced risk parameters are currently in place for perpetual markets, and maximum perpetual position sizes are capped at \$1,000,000 per market, per account.
Tokenized-stock collateral allocated to the user's Perps balance has its own Public Beta risk parameters for perpetual trading: each asset has a collateral cap on the amount that counts toward margin (see [Funding Ondo Perps](/funding-ondo-perps)); maximum allowed USDC Debt is 30% of your Non-USDC Margin Value; and total USDC Debt is capped at \$100,000 per account. These parameters are conservative for Public Beta and may be adjusted. See [Collateral Management](/collateral-management) for how these limits work.
During Public Beta, API key creation is enabled and API usage is rate limited to 1 request per second by default. Traders may request a higher limit by contacting [**support@ondoperps.xyz**](mailto:support@ondoperps.xyz) with their account ID, requested rate limit, and a brief description of their use case.
Frequent updates will be deployed during Public Beta. Platform availability may be briefly affected during scheduled or unscheduled maintenance windows. Safeguards are in place to minimize impact on traders.
If any issues arise, please contact support. For any questions, feedback, or support, traders can reach us at **[support@ondoperps.xyz](mailto:support@ondoperps.xyz).**
For full terms and conditions, please see the **[Terms of Use](https://app.ondoperps.xyz/terms-of-use).**
# Measuring Risk
Source: https://docs.ondoperps.xyz/risk-measures
Understanding your risk exposure is critical when trading with leverage. The following metrics are available on the platform to help you monitor and manage your positions.
These metrics describe risk in the user's Perps balance and open perpetual positions. Spot trading is fully funded and does not create a Perps margin requirement. Assets allocated to the user's Spot balance are not subject to Perps liquidation.
| Term | Definition |
| :- | :- |
| Maintenance Margin Requirement | The total maintenance margin required across all open positions. Liquidation is triggered when Margin Balance falls below this value. |
| Margin Ratio | A measure of proximity to liquidation. Returns `0` with no open positions, above `0.5` indicates elevated risk, and `1` triggers liquidation. |
| Account Leverage | The notional value of all open positions divided by Margin Balance. New positions cannot be opened once Account Leverage reaches the contract's Max Leverage. |
| Under Liquidation | Indicates whether the user's Perps balance is undergoing liquidation. |
When using tokenized stock collateral with open positions, a decline in your collateral's price reduces your Margin Balance, so your Margin Ratio can rise toward liquidation even if your positions have not moved.
# Settlement
Source: https://docs.ondoperps.xyz/settlement
## **Auto-Exchange**
Auto-Exchange (AE) sells collateral allocated to the user's Perps balance to cover USDC Debt. When debt reaches an applicable threshold, AE sells enough eligible collateral for USDC to clear that debt.
Auto-Exchange sells collateral allocated to the user's Perps balance. It does not draw on assets allocated to the user's Spot balance, including where the same tokenized stock is reflected in both.
Auto-Exchange is not the same as liquidation. AE converts collateral to USDC and your positions stay open. [Liquidation](/def) closes positions and does not directly sell your collateral tokens.
Clearing your debt affects your USDC balance differently depending on what's driving it:
* If your debt comes entirely from realized losses and fees (negative USDC balance, no uPnL), AE brings your USDC balance back to zero.
* If unrealized losses on open positions are contributing to your debt, AE brings your USDC balance *positive* enough to offset those unrealized losses. Your positions stay open, so the uPnL is still there. The positive USDC balance counteracts it, bringing your net debt to zero.
This is important: after AE, you may see a positive USDC balance even though you didn't fund USDC. That positive balance is not "free money," it's offsetting the unrealized losses on your still-open positions.
Your positions stay open. You keep trading. You just have less collateral afterward.
### **Trigger Conditions**
AE fires when your **LTV reaches 30%.** AE also fires if your USDC Debt reaches the \$100,000 maximum, even if your LTV is below 30%. Both the 30% LTV threshold and the \$100,000 maximum are current [Public Beta](/public-beta) limits and may be adjusted.
When triggered, AE sells enough collateral to **clear your entire USDC Debt** (bring your USDC Debt to zero, which may mean a positive USDC balance if you have unrealized losses).
### **The Auto-Exchange Process**
1. The amount of collateral needed to cover your full debt is calculated.
2. If you hold multiple collateral assets, AE may sell from any of them. Only enough collateral is sold to cover your debt and any applicable fees.
3. Your collateral balance decreases and your USDC Debt returns to zero.
1. Collateral is sold to cover your negative USDC balance.
2. Collateral is sold to balance out your unrealized losses (pushing USDC balance positive to offset uPnL).
4. Your open positions stay open, AE does not close your positions.
5. New orders and withdrawals are prevented until Auto-Exchange completes.
From your perspective, this is near-instant: collateral goes down, USDC goes up, positions are unaffected.
### **Manual Auto-Exchange**
You can initiate AE from the UI whenever your USDC Debt exceeds the minimum threshold. Before you confirm, the UI shows you the notional value of your assets to be sold and estimated USDC you will receive.
This is useful if you want to proactively clear your debt, for example before a weekend when you prefer to reduce exposure.
### **How USDC Debt and Auto-Exchange Work Together**
USDC Debt and Auto-Exchange are two halves of the same system. USDC Debt lets you keep trading when your USDC runs out. Auto-Exchange makes sure that debt never gets too large. Here is the full cycle:
1. **Funding collateral:** SPYon with a reference value of USD 100,000 is allocated to the user's Perps balance and shown as a SPY equivalent balance. With a 10% haircut, it contributes USD 90,000 to margin. Your USDC Debt is \$0, LTV is 0%.
2. **You trade and incur costs:** You open positions, pay fees, and receive or pay funding. Losses come out of your USDC balance.
3. **Your USDC balance goes negative:** You now have USDC Debt. Your funded equities back this debt, so you can keep trading as long as the debt stays within limits.
4. **Your LTV rises:** Continued losses increase your debt. If your collateral token's price also drops, your LTV rises even faster because the same debt is measured against a smaller collateral base.
5. **LTV hits 30%:** Auto-Exchange fires.
6. **AE clears your debt:** Enough of your collateral is sold to bring your USDC Debt to zero. If you have unrealized losses on open positions, your USDC balance goes positive to offset them. The remaining underlying tokenized stock continues to support the user's Perps balance. Your positions stay open. LTV returns to 0%.
7. **The cycle can repeat:** If you keep trading and accumulate new debt, the same thresholds apply. Each time AE fires, you have less collateral but zero debt.
### **Auto-Exchange Example**
**Starting state:** SPYon with a reference value of USD 100,000 funded and allocated to the user's Perps balance, shown as a SPY equivalent balance. Credited margin: \$90,000 (10% haircut). USDC balance: -\$5,000 (from accumulated trading fees and a realized loss).
**Step 1:** Your open positions move against you, accumulating -\$22,000 in unrealized PnL. Combined with your existing negative USDC balance:
```text theme={null}
margin balance = (-$5,000) + $90,000 + (-$22,000) = $63,000
USDC Debt = $63,000 - $90,000 = -$27,000
LTV = $27,000 / $90,000 = 30%
```
AE triggers at 30% LTV. Note that the \$27,000 debt comes from two sources: \$5,000 in realized costs (negative USDC balance) and \$22,000 in unrealized losses.
**Step 2:** AE sells \$27,000 of SPYon at market value, converting it to USDC.
**After AE:**
| | Value |
| :- | :- |
| SPYon remaining (SPY-equivalent) | \$73,000 market value, \$65,700 credited margin |
| USDC balance | +\$22,000 |
| Unrealized PnL | -\$22,000 (positions still open) |
| USDC Debt | \$0 |
| LTV | 0% |
| Positions | Remain open |
Your USDC balance ends positive, not zero, because your positions are still open with -\$22,000 uPnL. AE needs your USDC balance to be positive enough to offset that. The \$27,000 from selling collateral first covered the -\$5,000 USDC hole, then pushed the balance to +\$22,000 to counteract the unrealized losses. Net debt: zero.
```text theme={null}
margin balance = $22,000 + $65,700 + (-$22,000) = $65,700
USDC Debt = $65,700 - $65,700 = $0
LTV = 0%
```
### **When Auto-Exchange Does Not Trigger**
* Closing a position with a realized loss does not trigger AE unless the resulting debt pushes your LTV past 30%.
* Partial position closes follow the same rule, AE only fires if a threshold is crossed.
* Closing all positions does not automatically trigger AE, it only fires if the thresholds are crossed.
# Spot trading
Source: https://docs.ondoperps.xyz/spot-trading
This page describes spot trading in supported Ondo Global Markets tokens for USDC. For Ondo Private Markets Notes, including their separate fees and collateral restrictions, see [Private Markets](/private-markets). Every spot order is fully funded from the user's Spot balance. Spot trading does not provide leverage, borrowing, or perpetual funding payments. A sell order uses an available token holding; it does not open a short position.
For tokenized stocks, order quantities, fills and balances use the displayed equivalent quantity under the corresponding asset symbol. The underlying asset bought, sold or held is the GM token. A supported withdrawal sends that token to the user's external wallet.
## What a buy does
A buy fill spends USDC from the user's Spot balance to purchase the corresponding Ondo Global Markets token. For example, a buy in `SPY/USDC` purchases SPYon for the user. The interface shows that holding as a SPY equivalent balance. The displayed balance is a representation of the purchased SPYon; the user does not receive a separate holding of SPY shares. A supported withdrawal sends the corresponding quantity of SPYon to the user's external wallet. A buy fee is deducted from the purchased asset and reduces the resulting displayed equivalent quantity. See [Fees](/fees).
The purchased tokenized stock can be sold for USDC, withdrawn to the user's external wallet where supported, or allocated to the user's Perps balance if eligible as collateral.
## What a sell does
A sell fill sells the corresponding quantity of the underlying GM token and credits USDC proceeds, less the sell fee, to the user's Spot balance. An order may fill partly or not at all. Only completed fills change the traded quantities.
## Prices and settlement
Prices and values display in USD; trades settle in USDC. Execution prices come from the spot order book. Prices are quoted for the displayed equivalent unit. The last price of the underlying security is not a guaranteed execution price, and a market order can fill at several prices as it consumes available liquidity.
## Order size and price requirements
Minimum order quantities and price increments vary by market. Orders must meet the selected market's requirements, even when you have enough available funds.
Orders are also subject to market availability, liquidity, and [price protection](/mark-price-protection).
## Placing a first spot trade
1. Fund Ondo Perps with a supported asset on a supported network.
2. Transfer an available amount to the user's Spot balance.
3. Select the market, review the displayed equivalent quantity and price, and submit a supported order type.
4. Review the resulting fills, fees and Spot balance.
Order submission is not execution. See [Positions Panel](/positions-panel) for order and fill records, [Balances](/balances) for availability, and [Transfers](/transfers) for moving value between balances.
## Build with the Spot API
See the [Spot API guide](/api-reference/spot-api-guide) for authentication, market discovery, balances, transfers, order management, and WebSocket subscriptions.
# Take Profit / Stop Loss
Source: https://docs.ondoperps.xyz/take-profit-and-stop-loss
TP/SL applies to perpetual positions.
Take profit and stop loss (TP/SL) orders automatically close a position when the mark price hits a specified level. They are reduce-only, meaning they can only close an existing position and never open or flip position direction.
TP and SL are linked as OCO (one cancels the other). When one triggers, the other is automatically cancelled. If the position is fully closed for any reason, all attached TP/SL orders cancel automatically.
**Two Ways to Set TP/SL**
*At Order Placement:* Add TP/SL directly on the order form when entering a trade. These apply only to the size of that specific order, allowing multiple partial TP/SL legs to coexist on the same position across successive entries.
*Via Risk Management Module:* Set or adjust TP/SL from the Risk Management Module in the positions table at any time after a position is open. This applies to the full position, with a limit of one TP and one SL per market.
**Order States**
| **State** | **Description** |
| :- | :- |
| Open | Active and waiting for trigger |
| Triggered | Price condition met |
| Filled | Order executed |
| Rejected | Order was non-reducing at trigger and was rejected |
| Cancelled | Manually cancelled, or cancelled by the paired order triggering |
**Execution behavior**
Stop orders execute as market orders against the live order book. Before executing, the system calculates the maximum quantity it can safely fill without triggering liquidation of the user's perpetual positions. This check accounts for the fact that a stop-loss closes a position at realized prices that may be worse than the mark price used to estimate available margin: as the order eats through price levels beyond the mark price, realized losses can exceed the margin cushion and trigger liquidation.
If the calculated maximum is zero because the user's Perps balance is already under liquidation or the available margin is fully consumed by unrealized losses, the order does not execute and is cancelled.
When liquidity is limited or the spread is wide, the maximum safe size may be less than the full position size. In that case the order executes for a reduced quantity. The position is partially closed and the remaining stop order is cancelled; there is no retry or re-queue.
A stop order is also cancelled without executing if:
* The user's Perps balance enters liquidation between the trigger and execution.
* The position is neutral at trigger time.
* The position direction has flipped since the order was placed.
**Limitations**
* Triggered as market orders only, stop-limit variants are not currently supported.
* Risk Management Module TP/SL covers the full position only. Cancel and replace to update.
* Once visible in Open Orders, TP/SL orders are cancel-only and cannot be edited.
* Partial legs set at order placement are fixed at creation size and do not auto-resize as the position changes.
**Builder Code**
`builderCode` attribution is set independently for each of the two ways to place TP/SL, it is never inherited across them. There is no centrally configured default fee rate for a builder code — `builderCode.feeRateBpsFractional` must be set explicitly for a fill to earn commission; omitting it earns nothing, regardless of which path below is used.
* *At Order Placement:* a `builderCode` on the order request is inherited by that order's `takeProfit`/`stopLoss` legs. When one of those legs later triggers and closes the position, the closing fill carries the same builder code (and fee rate) as the order that created it.
* *Via Risk Management Module:* `POST /v1/perps/stop_order` accepts its own optional `builderCode`. It must be set explicitly on this request for the resulting close to earn builder commission — it does not inherit the `builderCode` from the order(s) that originally opened the position.
# Technology
Source: https://docs.ondoperps.xyz/technology
Ondo Perps runs as a decentralized offchain exchange powered by secure enclave technology, a hardware-based trusted execution environment (TEE) that isolates trading operations from external access or tampering, including by the platform itself. The result is execution speed comparable to a centralized exchange, with custody and integrity guarantees that no single party, including the operators, can override.
## The Three Building Blocks
Order matching, margin, liquidations, and wallet logic all execute inside Intel SGX enclaves. Code is encrypted at runtime and invisible to the host OS, cloud provider, and Ondo Perps operators.
An independent quorum of third-party attestors verifies that the enclave is running the published codebase on genuine SGX hardware. Key material is split across the network, no single party can move funds.
Funding is sent to the address provided for the selected asset and network. Funding transfers and withdrawals are recorded on-chain. The interface shows the user's corresponding balances.
## How It Works
### Secure Enclave Execution
At the core of the architecture is **Intel SGX (Software Guard Extensions)**, which runs the exchange's critical components, order matching, margin calculations, wallet management, and liquidations, inside an encrypted, hardware-isolated environment. Code running inside the enclave cannot be accessed or modified by:
* The operating system on the host machine.
* The infrastructure provider hosting the machine.
* The exchange operators themselves.
All orders are processed in fully encrypted form. Trade history is only revealed after an order is fully processed, so no actor, including the platform, can observe or interfere with the order flow in transit.
### Decentralized Attestor Network
Integrity is enforced by a decentralized network of independent third-party attestors. Their job is to continuously verify that the enclave is running the **correct, unmodified codebase** on **genuine SGX-enabled hardware**.
* **Distributed key shares:** Secret key material (including any key authorized to move user funds) is split across the attestor network. No single party can reconstruct the master secret or move funds unilaterally.
* **Quorum-gated upgrades:** Any modification to the codebase requires an independent majority of attestors to audit and approve before it takes effect.
### Onchain Custody
Funding and withdrawals move tokens on-chain; the enclave processes trading and balance records.
1. **Funding address:** A supported transfer to the funding address is reflected in the user's balance.
2. **Operational wallet:** Funded tokens are swept into an operational hot wallet.
3. **Withdrawals:** An approved withdrawal sends the corresponding asset from that wallet to the user's external wallet.
On-chain funding and withdrawal transactions are publicly observable. Trading and transfers between the user's Spot and Perps balances are reflected in off-chain records.
See [Funding Ondo Perps](https://docs.ondoperps.xyz/funding-ondo-perps) for the supported networks and the funding-address setup.
## Performance
Because critical operations execute inside the enclave rather than on-chain, the system does not carry the latency overhead typically associated with on-chain settlement. Order routing, margin updates, and liquidations are processed in real time, with execution speed comparable to a centralized exchange, including during periods of high volatility.
## What This Gives You
Compared with a traditional centralized exchange:
* **No operator front-running:** Operators cannot read your orders before they match.
* **No silent code changes:** Codebase upgrades require independent attestor approval.
* **Publicly observable funding and withdrawals:** Funding and withdrawal transactions are recorded on-chain.
Compared with a fully onchain DEX:
* **CEX-level latency:** Matching, margin, and liquidations are real-time, not bound by block times.
* **Richer order types:** Limit, TP/SL, TWAP, reduce-only, and post-only are all native ([Order types](https://docs.ondoperps.xyz/order-types)).
* **Lower fees with the same trust:** Offchain execution removes per-trade gas costs while preserving on-chain custody.
For users trading tokenized real-world assets, where accurate pricing, fair execution, and reliable collateral handling are foundational, this is the combination the platform is built for.
## Learn More
Supported collateral, networks, and funding addresses.
How external oracle prices keep liquidations fair and resistant to manipulation.
The multi-layered liquidation process and the insurance fund.
Where mark prices come from and how indices and futures are synthesized.
# TWAP
Source: https://docs.ondoperps.xyz/time-weighted-average-price
Time-weighted average price (TWAP) orders break a large order into smaller child orders executed at scheduled intervals over a specified duration, reducing market impact and slippage. By spreading execution over time, TWAP keeps the realized average price within a few basis points of the prevailing mid-price without sweeping the order book. TWAP orders are available on perpetual markets only.
*Example: Rather than buying a \$500,000 NVDA position in a single order, a TWAP breaks this into 20 orders of \$25,000 each executing every 5 minutes over 100 minutes, reducing price impact on each fill and delivering a better average execution price.*
**Placing a TWAP Order**
1. Select TWAP from the order form.
2. Enter the total order size in USDC or the base asset.
3. Set the duration using the hour and minute fields.
4. Select an order interval (default: 30 seconds; options: 1 minute, 5 minutes, 15 minutes, 1 hour).
5. Optionally enable price range bands to restrict execution to an acceptable price range.
6. Optionally enable randomization to vary order sizes and timing.
**Execution Parameters**
| **Parameter** | **Details** |
| :- | :- |
| Duration | 5 minutes to 7 days |
| Interval | Default 30 seconds. Options: 1 minute, 5 minutes, 15 minutes, 1 hour |
| Price Bands | When enabled, child orders are skipped if the mark price falls outside the configured range. Skipped quantity rolls forward as carryover to the next valid tick |
| Randomization | When enabled, order sizes and timing are randomized across the duration, making the execution pattern less predictable while still filling the full specified size |
| Slippage Protection | Each child order is protected by a maximum 3 bps slippage guard from the mid-price at the time the TWAP order was placed |
**Monitoring TWAP Orders**
TWAP orders are visible in the positions table under the TWAP column, with three tabs:
| **Tab** | **Details** |
| :- | :- |
| Active | Running TWAP orders including market, total size, executed size, average price, running time, and start time |
| History | Completed TWAP orders with execution summaries including total executed size, average fill price, runtime, and status (Filled, Cancelled, or Terminated) |
| Fill History | All child fills across all TWAP orders with granular detail, execution price, size, fee, and PnL per child fill |
TWAP orders can be cancelled directly from the TWAP modal.
**Key Rules**
* Child orders execute as market orders at each scheduled interval.
* Orders persist through system restarts, if downtime occurs during a scheduled tick, execution resumes on the next valid tick.
* Failed child orders are skipped and their quantity rolls into carryover for the next tick.
* The full margin for a TWAP order is reserved upfront at placement.
* Maximum of 20 active TWAP orders per account.
**Important Considerations**
*TP/SL interaction:* If a take profit and stop loss (TP/SL) order triggers while a TWAP is active, the TP/SL will close the existing position while the TWAP continues placing child orders as scheduled. Monitor both order types carefully when used simultaneously.
*Market hours:* TWAP orders cannot be created if execution would extend into after-hours trading windows. The duration must account for market trading hours, and any impact on order duration is displayed on the order form.
*Minimum order size:* If any child order would fall below the minimum order size, the order cannot be submitted. Increase the total order size or select a longer interval to resolve.
*Insufficient balance:* The full margin is reserved at placement. The TWAP terminates if sufficient Perps margin is no longer available.
# Transfers
Source: https://docs.ondoperps.xyz/transfers
A transfer reallocates an available amount between the user's Spot and Perps balances without an on-chain send or a trade. For tokenized stocks, the amount is entered as a displayed equivalent quantity; the underlying tokenized stock remains unchanged.
For example, transferring 5 SPY equivalent from a Spot balance showing 6.21 SPY equivalent to an otherwise empty Perps balance leaves 1.21 SPY equivalent in Spot and 5 SPY equivalent in Perps. The total remains 6.21 SPY equivalent, assuming no other activity. The underlying asset is SPYon throughout.
A transfer does not sell the asset for USDC or change its display scale. The displayed equivalent quantity deducted from the source is added to the destination.
## Transfers are not charged
Moving an available amount between the user's Spot and Perps balances is not charged. Trading fees apply to spot fills, and withdrawal charges apply to on-chain withdrawals. Neither applies to a transfer between balances.
## Making a transfer
Select the source balance, the destination, the asset and the amount. Review the displayed quantity to be moved and any collateral consequence before confirming, then check the resulting source and destination balances. A submitted request is not evidence that the destination balance was updated.
## Collateral consequences
Eligible assets, including supported tokenized stocks, contribute collateral when allocated to the user's Perps balance, whether through funding or a transfer from Spot. Assets allocated to the user's Spot balance do not back perpetual positions. Not every asset available for spot trading is eligible as Perps collateral.
An asset allocated to the user's Perps balance can be used to cover a shortfall on an open perpetual position, including through a sale by Auto-Exchange. Assets allocated to the user's Spot balance are not used for that purpose.
## Transfer limits
Open orders and pending withdrawals can hold part of the source balance. Moving value out of the user's Perps balance also depends on current free margin, so positions, losses, debt or changed collateral values can reduce the transferable amount, including after an earlier successful transfer.
## Incomplete transfers
An incomplete transfer may require support recovery. Reconciliation uses both balances, the asset, quantity, direction, time and the transfer reference. An unknown outcome does not confirm a rollback or a safe retry.
# Weekend Mechanics
Source: https://docs.ondoperps.xyz/weekend-mechanics
Weekends are defined as Friday 8:00 PM ET through Sunday 8:00 PM ET. US market holidays are treated the same as weekends.
### **Weekend Pricing**
Ondo Perps provides live pricing for tokenized equity collateral through the weekend. Your collateral is marked to market continuously, including on weekends and market holidays.
There is no price freeze, no queue, and no Monday reconciliation step. All user-facing surfaces reflect continuous weekend pricing.
During a corporate action, the collateral reference price for the affected asset can be held fixed while the adjustment is processed. That is separate from normal weekend pricing. See [Corporate actions](/corporate-actions).
### **Weekend Liquidations**
Both your perp positions and your collateral mark to market in real time over the weekend. Liquidations trigger based on real-time perp PnL against real-time collateral marks.
### **Closed-Market Settlement Fee**
If Auto-Exchange fires during weekends or holidays, a closed-market settlement fee applies, currently 2.5% of the debt repaid. This fee reflects the additional cost of converting collateral while the underlying equity market is closed and on-chain liquidity is thinner.
* The fee is funded from your own collateral (not a separate USDC charge). The exchange sells slightly more collateral to cover the fee alongside your debt
* The fee will be displayed on the autoexchange details
**Example:** If AE fires over a weekend to cover \$1,000 in USDC Debt, the exchange sells \$1,025 worth of collateral: \$1,000 to clear your debt and \$25 as the settlement fee (2.5%).
# Weekend and Extended Hours Trading
Source: https://docs.ondoperps.xyz/weekend-trading
The mechanisms on this page describe perpetual pricing. Spot markets execute at order-book prices and are not subject to mark price or discovery bounds. Spot orders are checked against the oracle price for the underlying before they enter the book. See [Markets](/markets) for spot trading hours.
## Oracle Price
The oracle price serves two critical functions:
* As the **reference price for funding rate** calculations.
* As a direct **input to the mark price** calculation.
Ondo Perps lists perpetuals on real-world assets whose underlying markets close on weekends and holidays. The exchange operates 24/7, but external price feeds go dark from Friday's equity close to Sunday evening (U.S. ET). To maintain continuous pricing, the system prioritizes external pricing when available and switches to an internal mechanism when it is not.
### External Pricing
During regular market hours, the oracle price is derived from external price feeds (Pyth, Stork, and other institutional data providers) that consume live market data for the underlying assets. The externally-derived fair price is used directly as the oracle price.
When external markets close at 4:00 PM ET on Friday, these feeds effectively freeze. The last traded price is broadcast on repeat, but no genuine price discovery occurs through external feeds until markets reopen.
### Internal Pricing
When external pricing becomes unavailable, the oracle switches to an **internal pricing session** that derives a continuously updating price from on-platform order book activity.
The internal oracle initializes from the last available external price at Friday's close. From that anchor, it tracks the exchange's own price discovery by measuring how far the order book has drifted from the current internal price.
This drift is captured by the **impact price difference (IPD)**:
```text theme={null}
IPD = max(Pb - S, 0) - max(S - Pa, 0)
```
Where:
* `S` is the previous internal oracle price
* `Pb` is the impact price on the bid (buy) side of the order book
* `Pa` is the impact price on the ask (sell) side of the order book
If the order book is skewed with more buying pressure, IPD is positive and the oracle moves up. If selling pressure dominates, IPD is negative and the oracle moves down. If the book is balanced around the current price, IPD is near zero and the oracle holds steady.
The IPD is then smoothed using a **continuous-time exponentially weighted moving average (EWMA)**:
```text theme={null}
S_t = β_t · S_{t⁻} + (1 - β_t) · x_t
β_t = exp(-Δt* / τ)
x_t = S_{t⁻} + IPD_t
```
Where:
* `S_t` is the new oracle price at time `t`
* `S_{t⁻}` is the previous oracle price
* `τ` is the time constant (1 hour)
* `Δt*` is the elapsed time since the last update (in seconds)
The continuous-time form means the EWMA decays correctly regardless of how frequently updates arrive, handling sparse weekend updates without accumulating staleness error.
### Session Transitions
When external markets close, the internal pricing session begins automatically, initializing from the last external price. When external data resumes (either from equity futures reopening Sunday evening or Monday's cash session), the oracle snaps back to the externally-derived price on the next update.
***
## Mark Price
The mark price is the price used for margining, liquidations, stop/limit triggers, and unrealized P\&L. It is intentionally separate from the oracle price to provide manipulation resistance: no single price input can simultaneously distort both funding rates and liquidation triggers.
### Three-Component Median
The mark price is computed as the **median of three components**:
| **Component** | **Description** |
| :- | :- |
| **Oracle price** | The current oracle price, whether externally-derived during market hours or internally-derived during the weekend session. |
| **Oracle price + 150s EMA of basis** | The oracle price plus a 150-second continuous-time EWMA of the difference between the perpetual's mid-price and the oracle price. This captures the current premium or discount of the perp relative to the oracle and smooths it over a 2.5-minute window. |
| **Median of best bid, best ask, and last trade** | The exchange's own top-of-book reference: the median of the best bid, best ask, and the most recent fill price. |
The mark price is the **median** of (1), (2), and (3). Taking the median rather than averaging means a single outlier component, such as a temporarily spiked last trade during thin weekend liquidity, cannot move the mark price beyond what the second-closest component allows.
### Clamping
Mark price updates are clamped to **\~50 basis points** of the current mark price per update cycle. This prevents a single large oracle jump from instantly moving the mark price by the full amount. Instead, the mark price steps toward the new level over successive updates.
This is particularly important at the Sunday re-open, when the external price snaps back from the internal weekend price and may carry a gap.
### What Each Price Drives
The **mark price** drives:
* Unrealized P\&L calculation
* Margin ratio and available margin
* Liquidation and ADL triggers
* Stop-loss and take-profit order execution
The **oracle price** separately drives:
* Funding rate calculation
* Reference price for discovery bounds
This separation ensures that a spike or manipulation in one component cannot simultaneously distort both the funding rate and the liquidation price.
***
## Discovery Bounds
When external markets close, Ondo Perps traders continue to buy, sell, and speculate on an asset's fair price. To provide a safe venue for this extended-hours price discovery, the mark price is constrained within a defined range around the Friday closing price.
### How Discovery Bounds Work
The discovery range is determined by the market's maximum leverage:
```text theme={null}
Upper bound = Friday close price × (1 + 1 / max_leverage)
Lower bound = Friday close price × (1 - 1 / max_leverage)
```
**Example:** If TSLAon closed at \$200 on Friday and the market's max leverage is 20x:
```text theme={null}
Upper bound = $200 × 1.05 = $210
Lower bound = $200 × 0.95 = $190
Tradeable range: $190 – $210 (±5%)
```
The internal oracle price and mark price can move freely within this range based on genuine order book activity. Once the oracle reaches the upper or lower bound, no further price discovery occurs in that direction. Trading may continue within the established range, but the oracle is capped at the bound.
### Order Protection
The discovery bounds also serve as a valid trading region. Any order that would execute at a price outside the bounds is rejected. This prevents traders from buying above or selling below prices that have deviated too far from the last external anchor.
### When Bounds Reset
The bounds are released when external pricing resumes. The oracle snaps back to the live externally-derived price, and the discovery bounds no longer apply until the next extended session begins.
### Risk Management
Traders should size positions and set stop-losses with the full discovery range in mind. For a 20x leverage market, the oracle can move up to \~5% from Friday's close during the weekend. If your liquidation price lies outside the active discovery bounds, your position cannot be liquidated from price movement alone while those bounds are in effect.
***
## Funding Rate
Funding rates on perpetuals keep the perp price anchored to the oracle price. When the perp trades at a premium to oracle, longs pay shorts. When it trades at a discount, shorts pay longs. The funding rate is calculated using the oracle price as its reference.
### Weekend Funding
During extended sessions, the oracle price is derived from the internal pricing mechanism (see Oracle Price above) rather than external feeds. Funding continues to accrue normally using this internally-derived oracle as its reference, so the funding rate remains meaningful even when external markets are closed.
If the internal pricing mechanism lacks sufficient order book data, the premium index is zeroed out rather than calculated from stale or manipulable inputs. This ensures funding payments are never driven by unreliable price signals.
### Dampening Multiplier
Tokenized equity perpetuals carry structural differences from crypto perpetuals. Traditional assets have lower baseline volatility and a known carry cost. To reflect this, Ondo Perps applies a **0.5x dampening multiplier** to the funding rate formula for equity perp markets.
This reduces the baseline annualized funding rate from approximately 11% to approximately 5.5%, which:
* Better reflects the carry cost of holding exposure to traditional assets
* Reduces the feedback loop between funding payments and internal price discovery during weekends, where thin liquidity can amplify funding-driven price moves
# Withdrawals
Source: https://docs.ondoperps.xyz/withdrawals
Supported withdrawals send assets from the user's Spot or Perps balance to an external wallet. Withdrawals from Perps must also leave sufficient margin to support open perpetual positions.
**Withdrawing a collateral asset**
Withdrawing a displayed SPY or QQQ equivalent balance sends the corresponding underlying tokenized stock, SPYon or QQQon, to the user's external wallet, whether the holding was funded or purchased on Spot. The displayed symbol is a UI representation, not a separate asset. See [Collateral Assets](/collateral-assets) for details.
**Withdrawing a Private Markets note**
A supported Private Markets withdrawal sends the note from your Spot balance to your external wallet. It does not sell or redeem the note, or deliver shares in the Reference Company. Check the supported asset and network; open orders, pending withdrawals, platform restrictions and the Issuer's transfer controls can affect availability. See [Private Markets](/private-markets#funding-and-withdrawals).
**How much can be withdrawn from Perps**
Your withdrawable amount is the smaller of two values: the margin you hold above what your open positions require, and the balance you actually hold.
```text theme={null}
Withdrawable = max(0, min(Margin Balance - Used Margin, Wallet Balance))
```
* **USDC:** You can withdraw up to the lesser of your withdrawable margin and your USDC balance.
* **Non-USDC:** You can withdraw up to your token balance, as long as your withdrawable margin allows it. When withdrawable margin is the binding constraint, it is converted to a token amount at the asset's post-haircut price.
**Three margin rules govern Perps withdrawals:**
1. **Unrealized profit is never withdrawable.** It can free up reserved margin, but it cannot be cashed out until you close the position and realize the profit.
2. **Unrealized losses reduce your withdrawable amount dollar-for-dollar.**
3. **A negative USDC balance blocks USDC withdrawals and reduces token withdrawals.** You cannot withdraw USDC you do not have. Your tokens stay withdrawable, but USDC Debt is part of your Margin Balance, so it lowers your withdrawable margin dollar-for-dollar.
**The haircut does not reduce what you own.** It applies only to margin credit. With no open positions and no USDC Debt, you can withdraw your full funded token quantity. Open positions and USDC Debt reduce your withdrawable margin, which can limit how much you can withdraw at a time. Token withdrawals carry no withdrawal fee; USDC withdrawals may include one.
**Additional rules for Perps withdrawals:**
* If your full funded amount is required to back open positions, no withdrawal is permitted.
* Mark price is used to determine withdrawal eligibility in real time.
* A supported withdrawal sends the corresponding underlying tokenized stock: a SPY equivalent balance represents SPYon, and a QQQ equivalent balance represents QQQon. You cannot withdraw a tokenized stock balance as USDC.
**If a withdrawal is blocked**, the UI explains why and suggests what you can do to unlock it (e.g., close a position or fund additional USDC).
**Withdrawal networks**
Available networks depend on the asset, not on the network used to fund.
* **USDC** can be withdrawn on **Ethereum or Arbitrum**, selected at the time of the request. Withdrawal is not restricted to the funding network: USDC funded on Ethereum can be withdrawn on Arbitrum.
* **Tokenized stocks** are withdrawn on **Ethereum**, as the corresponding Ondo Global Markets token.
The destination address must support the selected network. A withdrawal cannot be recalled once sent.
**Withdrawals below the minimum transferable amount**
A tokenized-stock withdrawal sends the underlying GM token to the user's external wallet. The requested displayed equivalent quantity is used to calculate the corresponding token quantity. If that quantity is below the smallest transferable amount at the token's supported precision, the withdrawal cannot be sent.
A small holding may instead be sold for USDC on a supported spot market if it meets that market's order requirements. Withdrawal of the proceeds remains subject to applicable withdrawal requirements. There is no minimum funding amount.
Each withdrawal specifies the user's source balance. Supported withdrawals draw from either the user's Spot balance or Perps balance; a transfer between balances is not a prerequisite. Perps withdrawals are additionally subject to margin requirements. Spot withdrawals are not subject to Perps margin requirements, though holds and applicable restrictions still apply.