From ee6727ccfc8d6d64c741091992c3c8c70b14e1e6 Mon Sep 17 00:00:00 2001 From: prudentdev-xyz Date: Mon, 31 Aug 2026 03:34:53 +0100 Subject: [PATCH] docs: add trading and positions guides, risk and order-types concepts, and visual regression tests DX-079: add /guides/trading and /guides/positions with UI field mappings. DX-078: add /concepts/risk with unhedged enumeration of protocol, market, LP, and oracle risks. DX-076: add /concepts/order-types with order lifecycle and execution guarantee documentation. DX-057: add e2e/docs-visual.spec.ts visual regression suite covering layout shell, kitchen-sink MDX, sidebar, TOC, search dialog, and 404. Refs #591, #590, #569, #588 --- ...docs-trading-positions-risk-order-types.md | 8 ++ apps/docs/CONTRIBUTING.md | 18 +++ apps/docs/content/concepts/order-types.mdx | 113 +++++++++++++++++ apps/docs/content/concepts/risk.mdx | 102 ++++++++++++++- apps/docs/content/guides/positions.mdx | 110 ++++++++++++++++ apps/docs/content/guides/trading.mdx | 118 ++++++++++++++++++ apps/docs/content/meta.json | 11 +- apps/docs/src/components/DocsHome.tsx | 103 ++++++++++++++- apps/docs/src/lib/docs-pages.ts | 9 +- e2e/docs-visual.spec.ts | 112 +++++++++++++++++ 10 files changed, 696 insertions(+), 8 deletions(-) create mode 100644 .changelog/unreleased/641-docs-trading-positions-risk-order-types.md create mode 100644 apps/docs/content/concepts/order-types.mdx create mode 100644 apps/docs/content/guides/positions.mdx create mode 100644 apps/docs/content/guides/trading.mdx create mode 100644 e2e/docs-visual.spec.ts diff --git a/.changelog/unreleased/641-docs-trading-positions-risk-order-types.md b/.changelog/unreleased/641-docs-trading-positions-risk-order-types.md new file mode 100644 index 00000000..f9bf5f51 --- /dev/null +++ b/.changelog/unreleased/641-docs-trading-positions-risk-order-types.md @@ -0,0 +1,8 @@ +--- +type: added +area: docs +pr: 641 +breaking: false +--- + +Add documentation guides for trading and positions, concepts for risk and order types, and visual regression test coverage for the docs workspace. diff --git a/apps/docs/CONTRIBUTING.md b/apps/docs/CONTRIBUTING.md index 8810ddeb..09c98892 100644 --- a/apps/docs/CONTRIBUTING.md +++ b/apps/docs/CONTRIBUTING.md @@ -140,6 +140,24 @@ This builds the content and starts the Nitro dev server. Open the printed URL, find your page in the sidebar, and check it in both light and dark themes at a mobile and a desktop width. +## Visual regression testing + +Visual regression specs under `e2e/docs-visual.spec.ts` test the layout shell, kitchen-sink MDX fixture, sidebar, TOC, search dialog, and 404 page across light and dark themes on desktop and mobile viewports. + +To run the visual suite: + +```bash +bun run test:e2e -- docs-visual +``` + +To update baselines after deliberate UI token or chrome changes: + +```bash +bun run test:e2e -- docs-visual --update-snapshots +``` + +Review the resulting diffs in `e2e/docs-visual.spec.ts-snapshots/` before committing. + ## Sourcing claims Every statement about protocol mechanics must be traceable to contract code, diff --git a/apps/docs/content/concepts/order-types.mdx b/apps/docs/content/concepts/order-types.mdx new file mode 100644 index 00000000..c9bb5a6d --- /dev/null +++ b/apps/docs/content/concepts/order-types.mdx @@ -0,0 +1,113 @@ +--- +title: Order types +description: Execution mechanics, lifecycle states, and guarantee limits for market, limit, and trigger stop-loss orders in SO4. +updated: 2026-08-31 +status: stable +--- + +SO4 supports market orders, limit orders, trigger orders (take-profit and stop-loss), and spot swaps. Every order is recorded on Stellar via Soroban smart contracts and executed by off-chain keepers when price and slippage conditions are satisfied. + +The execution guarantee differs between order types: a market order prioritises immediate execution over price certainty, whereas a limit or trigger order guarantees a price condition but carries no guarantee of execution. + +## Supported order types + +The protocol distinguishes between order types based on position direction and execution triggers: + +| Protocol Order Type | Interface Name | Purpose | Execution Trigger | +| --- | --- | --- | --- | +| `MarketIncrease` | Market Long / Short | Open or expand a leveraged position immediately | Executed in the next ledger at current mark price | +| `LimitIncrease` | Limit Long / Short | Open or expand a position at a better price | Executed when mark price reaches or improves the limit | +| `MarketDecrease` | Market Close | Reduce or fully close an existing position | Executed in the next ledger at current mark price | +| `LimitDecrease` | Take-Profit (TP) | Close a position at a profitable price threshold | Executed when mark price crosses the profit target | +| `StopLossDecrease` | Stop-Loss (SL) | Cut losses when market moves against a position | Executed when mark price drops/rises to the stop price | +| `MarketSwap` / `LimitSwap` | Spot Swap | Exchange long/short tokens without leverage | Executed immediately or at target swap limit | + +## Execution mechanics and guarantees + +### Market orders (`MarketIncrease` / `MarketDecrease`) + +- **What it does**: Requests immediate execution against current pool liquidity. +- **When it executes**: Keepers pick up the transaction in the next confirmed Stellar ledger (typically 3 to 5 seconds). +- **What is not guaranteed**: The exact fill price is **not guaranteed**. The fill price equals the oracle mark price at the ledger when the keeper executes the transaction, plus price impact. To protect against adverse moves between submission and execution, orders enforce an `acceptablePrice` parameter calculated from your slippage tolerance. + +### Limit orders (`LimitIncrease` / `LimitDecrease`) + +- **What it does**: Sits in [DataStore](/reference/data-store) until market prices meet the limit condition. For a long limit buy, the mark price must be less than or equal to the limit price; for a short limit sell, the mark price must be greater than or equal to the limit price. +- **When it executes**: Keepers monitor oracle feeds and submit execution transactions when the limit threshold is crossed. +- **What is not guaranteed**: Execution is **not guaranteed**. If the market touches your limit price briefly without sufficient keeper throughput or immediately reverses, the order remains unfilled. + +### Trigger orders (`StopLossDecrease` / `StopIncrease`) + +- **What it does**: Sits dormant in `DataStore` until the oracle mark price crosses the specified trigger threshold, at which point it converts into a market order to close or increase the position. +- **Trigger price versus execution price**: The **trigger price** is the activation condition that signals keepers to execute the order. The **execution price** is the market price at the moment the keeper transaction confirms on Stellar. In volatile markets, the execution price is often worse than the trigger price. +- **When a stop does not protect a position**: + 1. **Gap risk**: If an asset price gaps downward across blocks without intermediate trades (for example, falling from 60,000 USD to 54,000 USD), a stop set at 58,000 USD executes at 54,000 USD, not 58,000 USD. + 2. **Liquidation precedence**: If market price drops so quickly that position equity falls below maintenance margin before the keeper executes the stop, the position undergoes [Liquidation](/concepts/liquidation). Liquidation takes precedence over resting stop orders. + 3. **Slippage freeze**: If the execution price exceeds the order's `acceptablePrice` limit, the contract freezes the order rather than filling it, leaving the position open until manually cancelled or liquidated. + +## Order lifecycle and states + +Orders follow a deterministic lifecycle implemented across the smart contracts and tracked by the indexer: + +```text + ┌──────────────┐ + │ CREATED │ ◀── Order written to DataStore & OrderVault + └──────┬───────┘ + │ + ▼ + ┌──────────────┐ Keeper fill succeeds + │ ACTIVE │ ───────────────────────────▶ ┌──────────────┐ + └──────┬───────┘ │ EXECUTED │ + │ └──────────────┘ + │ Price violates acceptablePrice + ▼ + ┌──────────────┐ User cancels order + │ FROZEN │ ───────────────────────────▶ ┌──────────────┐ + └──────────────┘ │ CANCELLED │ + └──────────────┘ +``` + +### State mapping + +The order states match implementation in `apps/s03-indexer/src/mappings/mappingHandlers.ts` and `apps/web/src/features/trade/hooks/useOrdersWithIndexer.ts`: + +- `CREATED` (`ord_crt` event): The order is validated by [ExchangeRouter](/reference/exchange-router), collateral is locked in [OrderVault](/reference/order-vault), and parameters are stored in [DataStore](/reference/data-store). +- `ACTIVE` / `UPDATED` (`ord_upd` event): The order is pending in the keeper queue awaiting trigger conditions. +- `FROZEN` (`ord_frz` event): A keeper attempted execution, but the transaction reverted (for example, mark price exceeded `acceptablePrice` slippage limit). Frozen orders require manual cancellation to return reserved collateral. +- `EXECUTED` (`ord_exe` event): A keeper successfully filled the order on-chain, updating the position balance and emitting a position event. +- `CANCELLED` (`ord_can` event): The user called `ExchangeRouter.cancel_order`, returning reserved collateral from `OrderVault` to the user's wallet. + +## Slippage and acceptable price + +Every order sent to `ExchangeRouter` specifies an `acceptablePrice` parameter (scaled to 30 decimals): + +- **Long increase / Short decrease**: `acceptablePrice = markPrice × (1 + slippagePct)` +- **Short increase / Long decrease**: `acceptablePrice = markPrice × (1 - slippagePct)` + +If the oracle price at keeper execution is worse than `acceptablePrice`, the contract rejects the fill. This prevents front-running and catastrophic execution during sudden oracle spikes. + +## Contract mapping + +Orders map directly to functions on the SO4 core contracts: + +- **Create order**: Invokes `ExchangeRouter.create_order` (see [ExchangeRouter reference](/reference/exchange-router#create_order)) with `CreateOrderParams`. +- **Cancel order**: Invokes `ExchangeRouter.cancel_order(caller, order_key)`. +- **Collateral custody**: Collateral tokens are transferred directly from the user's address to the [OrderVault](/reference/order-vault) contract. + +## Worked example + +A trader holds a 10x Long on `BTC/USD` entered at 60,000.00 USD with 100 USDC collateral (position size = 1,000.00 USD, 0.016667 BTC). + +The trader sets a Stop-Loss trigger order at 57,000.00 USD with 1.0% slippage tolerance: + +- **Trigger price**: 57,000.00 USD. +- **Acceptable price**: `57,000.00 × (1 - 0.01) = 56,430.00 USD`. +- **Scenario A (Normal execution)**: BTC price trends downward and reaches 56,950.00 USD. The keeper triggers and executes the stop at 56,920.00 USD (within the 56,430.00 USD limit). The loss is `0.016667 × (60,000.00 - 56,920.00) = 51.33 USD`. The remaining collateral of 48.67 USD is returned to the trader's wallet. +- **Scenario B (Gap move)**: BTC price gaps from 57,500.00 USD directly to 56,000.00 USD on an external exchange shock. When the keeper attempts execution at 56,000.00 USD, the price is below the 56,430.00 USD acceptable price. The order freezes. The trader must cancel the frozen order and market-close or risk subsequent liquidation. + +## Related + +- [Trading guide](/guides/trading) — How to place market, limit, and stop orders in the interface. +- [Positions guide](/guides/positions) — Managing open orders, tracking status, and modifying margin. +- [Liquidation](/concepts/liquidation) — Liquidation mechanics and precedence over resting stops. +- [Risk](/concepts/risk) — Gap risk, oracle staleness, and contract execution considerations. diff --git a/apps/docs/content/concepts/risk.mdx b/apps/docs/content/concepts/risk.mdx index 58290ffb..85045247 100644 --- a/apps/docs/content/concepts/risk.mdx +++ b/apps/docs/content/concepts/risk.mdx @@ -1,12 +1,104 @@ --- title: Risk -description: A concise overview of market, liquidation, oracle, contract, network, interface, and custody risks in SO4. -updated: 2026-08-24 +description: Honest enumeration of what can go wrong: market volatility, contract admin roles, oracle failures, LP exposure, and interface outages. +updated: 2026-08-31 status: stable --- -Using SO4 can result in partial or total loss of collateral, including through mechanisms outside the interface's control. +Using SO4 can result in the complete loss of your deposited collateral. Leveraged positions amplify price volatility and can be liquidated rapidly when market prices move against you, while protocol dependencies on external oracles, Stellar network validators, and smart contracts introduce operational and systemic risks that no interface can eliminate. -A leveraged position can cross its maintenance margin as prices move and be closed near its liquidation price. Other risks include volatile prices, adverse price impact, funding, unavailable liquidity, stale or incorrect oracle data, smart-contract defects, Stellar or RPC disruption, wallet compromise, and interface outages. +This page provides an unhedged enumeration of the risks inherent in using SO4 as a trader or liquidity provider. -The interface prepares transactions; your wallet authorises them and the protocol executes them. Review the [terms and plain-language disclosure](/resources/terms) before using the interface. +## Audit status + +**The SO4 smart contracts have not been audited by an independent security firm.** No external audit has been completed, and current deployments on Stellar are in testnet release phases. + +If you interact with the contracts, you are using unaudited software. You should never deposit assets you cannot afford to lose. For disclosure instructions and security contacts, review [Security](/resources/security). + +## Market and trading risks + +### Leverage and liquidation + +Leverage multiplies both potential gains and losses. When you deposit collateral to open a leveraged position, even small adverse price movements consume your margin equity: + +- If your position equity drops below the maintenance margin requirement (typically 1.0% of position size), off-chain keepers or liquidators trigger a liquidation (see [Liquidation](/concepts/liquidation)). +- In a liquidation, your remaining position collateral is seized to cover pool losses and pay liquidation fees. + +### Gap moves and slippage + +During periods of extreme market volatility or thin liquidity, prices can gap abruptly from one price level to another without executing intermediate trades: + +- A stop-loss or trigger order does not guarantee execution at your exact trigger price. If the market gaps through your stop level, the keeper executes at the next available oracle price, resulting in greater slippage than anticipated (see [Order types](/concepts/order-types)). +- If market prices gap past your liquidation price before a stop-loss executes, the position liquidates immediately. + +### Funding costs over time + +Perpetual markets use recurring funding fees to balance long and short open interest: + +- If you hold a position on the majority side of an unbalanced market (for example, holding long when open interest is predominantly long), you continuously pay funding fees to the minority side (see [Funding and fees](/concepts/funding-and-fees)). +- Funding fees accrue on every settlement epoch and reduce your collateral equity over time, which can pull your liquidation price closer even if the spot market price does not change. + +## Protocol and contract risks + +### Smart contract bugs + +SO4's core logic runs across Soroban smart contracts on Stellar, including [ExchangeRouter](/reference/exchange-router), [OrderVault](/reference/order-vault), [DataStore](/reference/data-store), and [SyntheticsReader](/reference/synthetics-reader): + +- Unforeseen defects, arithmetic precision errors, or logic oversights in contract code can lead to stuck collateral, failed order execution, or unintended token transfers. +- Soroban execution environments enforce strict CPU and memory resource limits; transactions exceeding footprint or resource budgets revert on-chain. + +### Admin capabilities and upgrade authority + +The protocol architecture separates parameter control and contract execution: + +- **DataStore roles**: Parameter changes (including base fee rates, maximum leverage caps, liquidation execution fees, and market enable flags) are restricted to addresses holding the `CONTROLLER` role in [DataStore](/reference/data-store). +- **Upgrade authority**: Soroban contracts are upgradeable by updating their executable WASM bytecode hashes. The contract admin keys hold the authority to execute contract upgrades or modify protocol role assignments. +- **Custody boundary**: Collateral deposited for active orders is locked in `OrderVault`. While `OrderVault` restricts withdrawals to validated contract operations, administrative authority over protocol routing contracts represents a centralized governance trust assumption. + +## Oracle risks + +SO4 relies on price oracles to value positions, determine liquidation eligibility, and validate order execution prices (see [Oracles](/concepts/oracles)): + +1. **Source fallback**: Prices are fetched in priority order from the SO4 oracle proxy, Pyth Hermes, Binance REST, and GMX v2 feeds. +2. **Staleness**: If all networked oracle endpoints fail, the client falls back to static dummy prices, and keepers cannot settle trades on-chain. +3. **Price manipulation**: If an attacker manipulates the primary price source on an external venue, artificial price spikes could trigger unwarranted liquidations before normal market parity restores. + +## Liquidity provider risks + +Depositing assets into SO4 GM liquidity pools carries distinct risk factors: + +- **Counterparty to trader PnL**: Liquidity providers act as the direct counterparty to platform traders. When traders make net profits, pool collateral decreases to pay those gains (see [Pools guide](/guides/pools)). +- **Asset exposure**: GM pool tokens represent a blended basket of index and collateral assets. Deposition subjects liquidity providers to underlying asset price declines. +- **Two-step withdrawal queue**: Liquidity withdrawals require a two-step process: submitting a withdrawal request followed by keeper execution on Stellar. You cannot exit instantly during extreme congestion. + +## Operational and infrastructure risks + +### RPC and indexer availability + +- The interface relies on Stellar Horizon and Soroban RPC nodes to simulate transactions and broadcast signed envelopes. RPC outages prevent order submission or balance refreshing. +- If the SubQuery indexer (`apps/s03-indexer`) becomes unavailable, historical order and trade charts pause, although live contract data can still be read through RPC calls. + +### What to do if the interface is down + +The web application at `so4.market` is an open-source client for the underlying Soroban contracts: + +- If the hosted website goes down, **your open positions and collateral remain safely recorded on the Stellar ledger.** +- **Recovery path**: Because SO4 contracts are fully permissionless on-chain, you can interact with them directly without the web UI: + 1. Run the interface locally by cloning the public repository and executing `bun run --cwd apps/web dev`. + 2. Use the Stellar CLI or Soroban SDK (`packages/contracts`) to invoke [ExchangeRouter](/reference/exchange-router) functions directly from your terminal (`create_order` to close positions, `cancel_order` to withdraw unexecuted collateral, or `claim_funding_fees` to extract accrued fees). + +## Worked example + +A trader opens a 10x leveraged Long position on `BTC/USD` with 100 USDC collateral at an entry price of 60,000 USD (position size = 1,000 USD): + +- **Normal liquidation**: With a 1.0% maintenance margin ($10 minimum equity), liquidation occurs if BTC drops 9.0% to 54,600 USD. +- **Gap risk scenario**: A sudden market shock causes the oracle price to gap directly from 56,000 USD to 52,000 USD (a 13.33% drop) without intermediate quotes. +- **Loss outcome**: The position equity is completely wiped out (-$133.33 loss exceeds the $100.00 collateral). The position liquidates immediately at the gap price, leaving 0 USDC remaining. + +## Related + +- [Liquidation](/concepts/liquidation) — Detailed maintenance margin mathematics and liquidation process. +- [Funding and fees](/concepts/funding-and-fees) — Fee calculation formulas and funding rate mechanics. +- [Oracles](/concepts/oracles) — The multi-tier oracle hierarchy and staleness handling. +- [Security](/resources/security) — Vulnerability reporting policies and audit disclosures. +- [Terms of use](/resources/terms) — Legal terms and risk disclosures. diff --git a/apps/docs/content/guides/positions.mdx b/apps/docs/content/guides/positions.mdx new file mode 100644 index 00000000..55803728 --- /dev/null +++ b/apps/docs/content/guides/positions.mdx @@ -0,0 +1,110 @@ +--- +title: Positions and orders +description: Monitor and manage active perpetual positions, resting limit and trigger orders, trade history, and claimable funding fees in SO4. +updated: 2026-08-31 +status: stable +--- + +The bottom panel of the SO4 trade page at `/trade` gives traders full visibility over active exposure, resting orders, past trade executions, and accrued funding claims. It comprises four primary tabs: **Positions**, **Orders**, **Trades**, and **Claims**. + +All table data is indexed from Stellar ledgers by SubQuery (`apps/s03-indexer`) and supplemented with live contract reads from [SyntheticsReader](/reference/synthetics-reader) when the indexer is paused. + +## Positions tab + +The **Positions** tab lists all currently open perpetual positions for the connected wallet address. + +### Columns + +Every column maps directly to contract and indexer state: + +| Column | Header | Description | +| --- | --- | --- | +| `market` | **Market** | Displays the base token icon, the market pair name (such as `BTC/USD`), and a direction badge (`Long` in green or `Short` in red). | +| `size` | **Size** | The total notional value of the position in USD (collateral multiplied by leverage). | +| `collateral` | **Collateral** | The current collateral value deposited in the position in USD. | +| `entry` | **Entry** | The volume-weighted average entry price at which the position was established. | +| `mark` | **Mark** | The latest oracle mark price used for valuation and margin checks. | +| `liq` | **Liq.** | The estimated liquidation price. When mark price comes within 10% of this value, the text turns red as a risk warning. | +| `pnl` | **PnL** | Net unrealised profit or loss in USD and percentage after accounting for opening fees, closing fees, and accumulated funding. Green indicates profit; red indicates loss. | +| `funding-fee` | **Funding Fee** | Accrued claimable funding fees in USD earned from holding a position on the minority side of the pool, or an em-dash (`—`) if non-positive. | +| `next-funding` | **Next Funding** | A real-time countdown timer (for example, `42m` or `1h 15m`) showing the time remaining until the next funding settlement epoch. | +| `actions` | *(Actions)* | Interactive buttons to manage or exit the position. | + +### Position action buttons + +The action buttons at the right of each row provide immediate management options: + +- **Share**: Generates a shareable URL containing the market and direction (`/trade?market=...&type=...`) and copies it to your clipboard. +- **+ Collateral**: Opens the collateral modification dialog in deposit mode. Adding collateral increases margin equity, lowers effective leverage, and moves your liquidation price further from the current mark price. +- **- Collateral**: Opens the collateral modification dialog in withdrawal mode to extract excess margin. The interface prevents withdrawals that would push effective leverage beyond the 50x protocol ceiling. +- **Claim**: Appears when positive funding fees have accumulated (`fundingFeeUsd > 0`). Calls `ExchangeRouter.claim_funding_fees` to withdraw fees directly to your wallet. +- **Close**: Submits a `MarketDecrease` order via `ExchangeRouter` with a 1.0% slippage tolerance to fully exit the position, realise final PnL, and return remaining collateral to your wallet. + +### Chart position lines + +When you hold an open position in the selected market, the chart draws real-time reference lines: + +- **Entry line**: Marked `${Side} Entry` (for example, `Long Entry`) as a green or red dashed line at your average entry price. +- **Liquidation line**: Marked `${Side} Liq.` as an amber large-dashed line at your liquidation price. + +## Orders tab + +The **Orders** tab tracks all unexecuted, resting, or conditional orders submitted by your account. + +### Columns + +| Column | Header | Description | +| --- | --- | --- | +| `market` | **Market** | The market pair name, direction badge (`Long` / `Short`), and an optional yellow `Frozen` badge if execution failed. | +| `type` | **Type** | The specific order type string: `MarketIncrease`, `LimitIncrease`, `MarketDecrease`, `LimitDecrease`, `StopLossDecrease`, `MarketSwap`, or `LimitSwap`. | +| `size` | **Size** | The requested position size delta in USD. | +| `trigger` | **Trigger** | The specified trigger price threshold for limit and stop orders in USD. | +| `created` | **Created** | The local timestamp when the order was submitted on Stellar. | +| `actions` | *(Actions)* | Includes a **Cancel** button to cancel resting orders and unlock reserved collateral. | + +### Order status lifecycle + +Orders move through explicit lifecycle states defined in the contracts and tracked by the indexer: + +1. `CREATED`: The order has been written to [DataStore](/reference/data-store) and collateral is reserved in [OrderVault](/reference/order-vault). +2. `ACTIVE`: The order is live and awaiting keeper execution once trigger and price constraints are met. +3. `FROZEN`: The keeper attempted execution, but the fill price violated the order's `acceptablePrice` slippage limit, or pool capacity was exceeded. A frozen banner notifies you to cancel or resubmit. +4. `EXECUTED`: Off-chain keepers submitted the fill transaction on Stellar, updating the underlying position. +5. `CANCELLED`: You cancelled the order, returning reserved collateral from `OrderVault` to your wallet. + +## Trades tab + +The **Trades** tab displays historical records of filled orders, closed positions, liquidations, and token swaps executed by your account. + +- Displays execution price, realised PnL, paid fees, and transaction timestamps. +- When no trade history exists or the indexer is syncing, an empty state card notifies the user. + +## Claims tab + +The **Claims** tab aggregates all positive funding fee balances accumulated across your positions across all markets: + +- **Total claimable**: Displays the combined USD value of all accrued funding fee rewards. +- **Claim All**: Submits a single batch transaction calling `claimFundingFees` across every market with a positive balance. +- **Market & Fee table**: Lists each individual market alongside its claimable USD fee amount. + +## Worked example + +A trader holds an open `Long BTC/USD` position: + +- **Position size**: 1,000.00 USD. +- **Current collateral**: 100.00 USDC (effective leverage: 10.0x). +- **Entry price**: 60,000.00 USD. +- **Estimated liquidation price**: 54,610.92 USD. + +The trader clicks **+ Collateral** and deposits an additional 50.00 USDC: + +- **New collateral**: 150.00 USDC. +- **New effective leverage**: `1,000.00 / 150.00 = 6.67x`. +- **New liquidation price**: With 10.00 USD maintenance margin, the position can now tolerate a loss of `150.00 - 10.00 = 140.00 USD` (a 14.0% adverse price drop). The updated liquidation price moves down to `60,000.00 × (1 - 0.14) = 51,600.00 USD`, increasing the buffer against market drops by 3,010.92 USD. + +## Related + +- [Trading guide](/guides/trading) — Placing orders, using leverage, and configuring slippage. +- [Order types](/concepts/order-types) — Execution mechanics for market, limit, and stop orders. +- [Liquidation](/concepts/liquidation) — How maintenance margin and liquidation triggers work. +- [Funding and fees](/concepts/funding-and-fees) — How funding rates accrue and how to claim fee rewards. diff --git a/apps/docs/content/guides/trading.mdx b/apps/docs/content/guides/trading.mdx new file mode 100644 index 00000000..3ac91294 --- /dev/null +++ b/apps/docs/content/guides/trading.mdx @@ -0,0 +1,118 @@ +--- +title: Trading +description: Panel-by-panel guide to the SO4 trade page: market selector, charts, order forms, leverage controls, and transaction confirmation. +updated: 2026-08-31 +status: stable +--- + +The SO4 trade page at `/trade` provides the interface to open leveraged perpetual positions or execute spot token swaps across supported markets. Every order created through the interface is sequenced on Stellar via Soroban smart contracts and executed by off-chain keepers. + +The interface is divided into three primary panels: the market header and chart on the left, the trade execution panel on the right, and the positions and order management tabs along the bottom. + +## Market selector and statistics + +The top header bar displays market statistics and lets you switch trading pairs. + +- **Market selector**: Located at the top left of the chart area. Click the dropdown to search and choose from active trading markets, such as `BTC/USD`, `ETH/USD`, and `XLM/USD`. +- **24h Volume**: Total rolling 24-hour traded volume in the selected market in USD. +- **Open Interest**: Total notional value of active open positions currently held in the market. +- **Mark Price**: The latest composite price derived from the oracle hierarchy (see [Oracles](/concepts/oracles)). +- **Index Price**: The underlying spot reference price. + +### System status indicators + +The header and chart top bar include real-time operational indicators: + +- **Oracle staleness dot**: A status dot next to the mark price indicating price age. Green indicates fresh data (under 5 seconds old), yellow indicates warning (5 to 30 seconds old), and red indicates a stale feed (over 30 seconds old). +- **Waiting for price update**: Displayed on the trade panel if live oracle data is temporarily unavailable. Order submission is paused until fresh attestations arrive. +- **Circuit breaker banner**: Appears across the top of the screen if protocol circuit breakers activate due to extreme market volatility or oracle deviations. + +## Chart panel + +The chart area visualises historical and live price action using TradingView Lightweight Charts: + +- **Timeframe periods**: Select candle intervals from `1m`, `5m`, `15m`, `1h`, `4h`, and `1D`. +- **Position entry line**: A green dashed horizontal line shows your volume-weighted entry price for an open long position, while a red dashed horizontal line shows your entry for an open short position. +- **Liquidation line**: A large-dashed amber horizontal line indicates your estimated liquidation price. +- **Accessible table view**: Screen readers and keyboard users can toggle an accessible data table listing representative open, high, low, and close (OHLC) values. + +## Trade panel + +The trade panel on the right side contains order input controls and execution parameters. + +### Trade types + +Select your intended action using the top segmented tabs: + +1. **Long**: Open a position that profits when the index price increases (see [Perpetuals](/concepts/perpetuals)). +2. **Short**: Open a position that profits when the index price decreases. +3. **Swap**: Exchange tokens directly within the pool without opening a leveraged position. + +### Order modes + +Choose the execution mode: + +1. **Market**: Executes immediately in the next ledger at the prevailing mark price subject to your slippage tolerance. +2. **Limit**: Submits a resting limit order that executes only when the market reaches your specified limit price. +3. **Trigger**: Submits a conditional order, such as a stop-loss or take-profit order, that triggers when the market crosses your target price (see [Order types](/concepts/order-types)). + +### Input fields + +- **Collateral / Pay**: Enter the amount of collateral to deposit. The interface shows your available wallet balance with a `Max` button to fill the full balance. Below the input, the equivalent USD value is shown in real time. +- **Market / Receive**: Indicates the index market pair (such as `BTC/USD`) for perpetual positions or the destination asset for spot swaps. +- **Limit price / Trigger price**: Visible when in Limit or Trigger mode. Enter the USD price threshold that must be reached before execution. + +### Leverage slider + +For `Long` and `Short` positions, the leverage slider adjusts exposure from **1.1x** up to **50x** (subject to market configuration in [DataStore](/reference/data-store)): + +- Moving the slider recalculates your total **Position size** in USD, which equals your deposited collateral value multiplied by the leverage multiplier. +- Increasing leverage reduces the distance between your entry price and your liquidation price (see [Margin and leverage](/concepts/margin-and-leverage)). + +### Trade information rows + +Before submitting, the trade panel displays cost and risk estimates calculated from contract rules: + +- **Entry price**: Estimated execution price including price impact. +- **Limit price**: The specified limit price when in Limit mode. +- **Liq. price**: Estimated liquidation price. Highlighted in red if the position is within 10% of liquidation. +- **Funding**: Current hourly funding rate paid or received, alongside a live countdown timer to the next funding settlement epoch (see [Funding and fees](/concepts/funding-and-fees)). +- **Position fee**: Base protocol fee charged on open and close (typically 0.05% to 0.10% of position size). +- **Price impact**: Percentage and USD change caused by your order relative to current pool depth. +- **Execution fee**: Small fee in XLM (typically ~0.01 to 0.03 XLM) paid to off-chain keepers to settle the transaction on Stellar. +- **Total fees**: The aggregate sum of position fees, price impact, and keeper fees in USD. + +### Advanced options + +Click **Show** under Advanced options to configure custom execution parameters: + +- **Slippage tolerance**: Sets the maximum acceptable price deviation (default: 1.0%, configurable from 0.1% to 10.0%). If the execution price at the time of keeper settlement is worse than `acceptablePrice`, the contract halts or freezes the order rather than filling at an adverse rate. + +## Confirmation step + +Clicking the action button (for example, **Long BTC/USD** or **Short ETH/USD**) opens the confirmation modal: + +1. **Review parameters**: Verify `Size`, `Leverage`, `Entry price`, `Price impact`, `Liq. price`, `Collateral`, and `Total fees`. +2. **Network fee**: Displays the simulated Stellar transaction resource fee. +3. **Execution fee**: Confirms the off-chain keeper transaction incentive. +4. **TP/SL sidecar orders**: Lists attached conditional take-profit or stop-loss trigger orders if configured. +5. **Submit**: Clicking **Confirm** requests a signature from your connected wallet (such as Freighter) and submits the transaction to the [ExchangeRouter](/reference/exchange-router#create_order) contract. + +## Worked example + +A trader opens a leveraged long position on `BTC/USD` with the following parameters: + +- **Collateral**: 100 USDC (valued at 100.00 USD). +- **Leverage**: 10x. +- **Position size**: 100 USDC × 10 = 1,000.00 USD (0.016667 BTC at 60,000 USD spot). +- **Price impact**: 0.02% (0.20 USD), setting the estimated entry price to 60,012.00 USD. +- **Position fee**: 0.10% of 1,000.00 USD = 1.00 USD. +- **Keeper execution fee**: ~0.01 XLM (~0.001 USD). +- **Liquidation price calculation**: With a maintenance margin requirement of 1.0% (10.00 USD minimum equity), the position liquidates when equity falls from 100.00 USD to 10.00 USD (a 90.00 USD loss, or 9.0% adverse price move on 10x leverage). The estimated liquidation price is `60,012.00 × (1 - 0.09) = 54,610.92 USD`. + +## Related + +- [Positions and orders](/guides/positions) — How to monitor open positions, edit margin, and track resting orders. +- [Order types](/concepts/order-types) — Execution mechanics and guarantees across market, limit, and trigger orders. +- [Liquidation](/concepts/liquidation) — Detailed maintenance margin rules and liquidation calculations. +- [Risk](/concepts/risk) — Protocol, oracle, and market risks associated with leveraged trading. diff --git a/apps/docs/content/meta.json b/apps/docs/content/meta.json index 8ec13c91..e393a7a1 100644 --- a/apps/docs/content/meta.json +++ b/apps/docs/content/meta.json @@ -20,6 +20,7 @@ { "label": "Concepts", "pages": [ + "concepts/order-types", "concepts/risk", "concepts/funding-and-fees", "concepts/liquidation", @@ -42,7 +43,15 @@ }, { "label": "Guides", - "pages": ["guides/pools", "guides/earn", "guides/referrals", "guides/faucet", "guides/troubleshooting"] + "pages": [ + "guides/trading", + "guides/positions", + "guides/pools", + "guides/earn", + "guides/referrals", + "guides/faucet", + "guides/troubleshooting" + ] }, { "label": "Resources", diff --git a/apps/docs/src/components/DocsHome.tsx b/apps/docs/src/components/DocsHome.tsx index 0c60efab..f67750e1 100644 --- a/apps/docs/src/components/DocsHome.tsx +++ b/apps/docs/src/components/DocsHome.tsx @@ -1,6 +1,24 @@ import { useEffect, useMemo, useRef, useState } from "react" import { KeyboardShortcut } from "@workspace/ui/components/keyboard-shortcut" import { docsPages, getPager } from "../lib/docs-pages" +import { Callout } from "@workspace/ui/components/callout" +import { + CodeBlock, + ContractAddress, + Mermaid, + ParamTable, + Steps, + TabItem, + Tabs, +} from "../mdx/components" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@workspace/ui/components/table" import { docsShortcuts, getPlatform, @@ -188,11 +206,12 @@ function Article({

Details

+ {page.kind === "kitchen-sink" ? : null} {page.kind === "long" ? : null} {page.kind === "tabs" ? ( ) : null} - {page.kind !== "long" && page.kind !== "tabs" ? ( + {page.kind !== "long" && page.kind !== "tabs" && page.kind !== "kitchen-sink" ? (

This placeholder route keeps the docs build emitting real HTML.

@@ -210,6 +229,88 @@ function Article({ ) } +function KitchenSinkContent() { + return ( +
+

Typography & Blocks

+

+ This fixture exercises inline code, bold text, and standard links to Risk. +

+ +
+ "Markdown blockquotes map directly onto semantic design tokens." +
+ + + This is a note callout providing informational context. + + + + This is a warning callout indicating caution is required. + + + + This is a danger callout warning against collateral loss. + + + + + + Market + Type + Max Leverage + + + + + BTC/USD + Perpetual + 50x + + + ETH/USD + Perpetual + 50x + + +
+ + + {`const client = new ExchangeRouterClient({ network: "testnet" });\nawait client.createOrder(params);`} + + + + +

Stellar smart contract transactions settle in under 5 seconds.

+
+ +

Soroban provides deterministic Rust-based contract execution.

+
+
+ + +
  • Connect your Freighter wallet to the SO4 interface.
  • +
  • Deposit testnet collateral from the faucet.
  • +
  • Submit your leveraged perpetual order.
  • +
    + + + + + + +
    + ) +} + function LongPageContent() { return (
    diff --git a/apps/docs/src/lib/docs-pages.ts b/apps/docs/src/lib/docs-pages.ts index 2bd7e6fc..9f91816a 100644 --- a/apps/docs/src/lib/docs-pages.ts +++ b/apps/docs/src/lib/docs-pages.ts @@ -1,4 +1,4 @@ -export type DocsPageKind = "short" | "long" | "tabs" | "search" +export type DocsPageKind = "short" | "long" | "tabs" | "search" | "kitchen-sink" export interface DocsPage { slug: string @@ -16,6 +16,13 @@ export const docsPages: Array = [ section: "Start", kind: "short", }, + { + slug: "/fixture/kitchen-sink", + title: "MDX Kitchen Sink", + description: "Committed fixture page exercising every custom MDX component.", + section: "Fixture", + kind: "kitchen-sink", + }, { slug: "/concepts/risk", title: "Risk Basics", diff --git a/e2e/docs-visual.spec.ts b/e2e/docs-visual.spec.ts new file mode 100644 index 00000000..878bf9e3 --- /dev/null +++ b/e2e/docs-visual.spec.ts @@ -0,0 +1,112 @@ +import { expect, test } from "@playwright/test" +import type { Page } from "@playwright/test" + +// DX-057: Visual regression coverage for the documentation app and chrome. +// +// Covers: +// - Layout shell (header, sidebar, TOC, content area, and pager navigation) +// - Kitchen-sink fixture exercising all custom MDX components (DX-034 to DX-038) +// - Sidebar navigation and active section indicators +// - On-page Table of Contents (TOC) with scroll-spy anchors +// - Search dialog in open state with query results +// - 404 error page for unmapped documentation paths +// +// Themes: light | dark +// Viewports: desktop (1280×800) | mobile (390×844) +// +// Stabilization strategy: +// - Theme is injected via addInitScript before page evaluation to prevent theme flashing. +// - System clock is installed and frozen using page.clock so timestamps and relative dates never drift. +// - Third-party network requests are intercepted and stubbed. +// - CSS animations and transitions are frozen via animations: "disabled" and reducedMotion: "reduce". +// +// Updating baselines: +// bun run test:e2e -- docs-visual --update-snapshots +// and review the resulting snapshots in your pull request. + +const VIEWPORTS = { + desktop: { width: 1280, height: 800 }, + mobile: { width: 390, height: 844 }, +} as const + +const THEMES = ["light", "dark"] as const + +const STATES = [ + { name: "shell", path: "/" }, + { name: "kitchen-sink", path: "/fixture/kitchen-sink" }, + { name: "sidebar", path: "/developers/architecture" }, + { name: "toc", path: "/concepts/risk" }, + { name: "404", path: "/non-existent-page" }, +] as const + +async function stubExternalNetwork(page: Page) { + await page.route("**/api.binance.com/**", (route) => + route.fulfill({ status: 200, contentType: "application/json", body: "[]" }), + ) + await page.route("**/oracle.biscotti-proxy-worker.workers.dev/**", (route) => + route.fulfill({ status: 200, contentType: "application/json", body: "[]" }), + ) +} + +for (const theme of THEMES) { + for (const [viewportName, viewport] of Object.entries(VIEWPORTS)) { + test.describe(`docs visual ${theme} theme, ${viewportName}`, () => { + test.use({ viewport, reducedMotion: "reduce" }) + + test.beforeEach(async ({ page }) => { + await stubExternalNetwork(page) + await page.addInitScript((t) => { + window.localStorage.setItem("so4-theme", t) + document.documentElement.classList.toggle("dark", t === "dark") + }, theme) + await page.clock.install({ time: new Date("2026-01-01T00:00:00Z") }) + await page.clock.pauseAt(new Date("2026-01-01T00:00:01Z")) + }) + + for (const state of STATES) { + test(state.name, async ({ page }) => { + await page.goto(state.path) + await page.waitForLoadState("networkidle") + + await expect(page).toHaveScreenshot( + `docs-${state.name}-${theme}-${viewportName}.png`, + { + fullPage: true, + animations: "disabled", + }, + ) + }) + } + + test("search-dialog", async ({ page }) => { + await page.goto("/") + await page.waitForLoadState("networkidle") + + // Open search dialog by clicking search button or triggering keyboard shortcut + const searchButton = page.getByRole("button", { name: /search/i }) + if (await searchButton.isVisible()) { + await searchButton.click() + } else { + await page.keyboard.press("ControlOrMeta+K") + } + + const searchDialog = page.getByRole("dialog") + await expect(searchDialog).toBeVisible() + + // Type a sample query to populate search results list + const searchInput = page.getByRole("textbox", { name: /query|search/i }) + if (await searchInput.isVisible()) { + await searchInput.fill("margin") + } + + await expect(page).toHaveScreenshot( + `docs-search-${theme}-${viewportName}.png`, + { + fullPage: true, + animations: "disabled", + }, + ) + }) + }) + } +}