# Phoenix (/docs/nodes/defi/phoenix)

Trade Solana perps on Phoenix (Ellipsis Labs) and read markets, orderbook, and trader state.



Phoenix is the Solana perpetuals DEX from Ellipsis Labs. The Phoenix node reads market and account data, places trades, and moves collateral from your connected Privy wallet.

Every trading and collateral operation runs in **paper mode by default**: it validates and simulates against the live market but broadcasts nothing until you explicitly flip the node to Live. So you can build and test a whole strategy before any real funds move.

Quick start (paper) [#quick-start-paper]

1. Add a Phoenix node and pick a trading op, e.g. **Place Market Order**.
2. Set `symbol` (e.g. `SOL`), `side` (Bid = buy, Ask = sell), `baseUnits` (size), and the required price cap `priceLimitUsd`.
3. Run it. The node stays in **paper** mode, estimates your fill against the live order book, and returns a simulated result (`simulated: true`, `signature: "PAPER"`). Nothing is signed or broadcast.
4. When you want real trades, flip the node's **PAPER / LIVE** switch and activate Phoenix Flight access once (see [Going live](#going-live-wallet-activation)).

You don't need wallet activation, funds, or a credential to paper-test.

What each operation needs [#what-each-operation-needs]

| Operation type                                                     | What it needs                                                                         |
| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
| Market data reads (`getMarkets`, `getOrderbook`, candles, funding) | Nothing. Public data, no wallet or credential.                                        |
| Account reads (`getTraderState`, history, PnL)                     | Your connected wallet address (read-only, no signature, no activation).               |
| Trading & collateral in **paper** mode                             | Just be signed in. No activation, funds, or signature.                                |
| Trading & collateral in **Live** mode                              | A wallet with Phoenix Flight access (one-time activation below) plus USDC collateral. |

Per-op details are in [Operations](#operations).

Paper trading (default) [#paper-trading-default]

Signed Phoenix nodes show a **PAPER / LIVE** switch on the node card. Read-only ops don't show it, they never broadcast. New nodes start in paper.

Paper mode runs the **exact same input validation as live**, then stops before signing: no wallet is resolved, no Flight whitelist is checked, nothing is broadcast. A config error fails identically in both modes, so paper is a faithful dry run. What it returns depends on the op:

* **Limit / market orders** estimate your fill by walking the live L2 order book (VWAP across price levels, capped at your limit or price cap). This accounts for depth and slippage, so it is stricter than a top-of-book fill on large orders. When the relevant book side is empty it falls back to the mid.
* **Post-only orders** check whether the price would rest as a maker or be rejected for crossing the spread, exactly as Live would reject it on-chain. You never get a false "resting" result that dies the instant you go live.
* **Stop-loss / conditional / cancel ops** preview the trigger or cancellation they would submit.
* **Collateral / subaccount ops** preview the USDC movement.

Flipping a node to LIVE opens a confirmation dialog (real funds move). Flipping back to PAPER is instant. The first time a **Live** signed op runs in a workflow, you are prompted once to authorize wallet signing; paper ops never prompt.

Simulated outputs [#simulated-outputs]

Every paper result carries `simulated: true` and `signature: "PAPER"` instead of a real tx hash, plus an op-specific preview field:

| Op(s)                                                                  | Preview field                                          | Contents                                                                                                                                                                                                                                                                             |
| ---------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| placeLimitOrder, placeMarketOrder                                      | `fill` (or `null`), plus optional `wouldRest` / `note` | Estimated `filledBaseUnits`, `avgPriceUsd`, `notionalUsd`, `feeUsd` (taker + builder fee), `partial`, `priceCapped`, `usedFallbackMid`. A limit order's unfilled remainder is reported under `wouldRest`; if nothing fills within the cap, `fill` is `null` and `note` explains why. |
| placePostOnlyOrder                                                     | `wouldRest`, **or the node fails**                     | If the maker price would NOT cross, returns `fill: null` + `wouldRest`. If it WOULD cross (a buy at/above the best ask, a sell at/below the best bid), the node **fails** with a cross-rejection error, exactly as a live post-only is rejected on-chain.                            |
| placeStopLoss, placePositionConditionalOrder                           | `wouldPlaceTrigger`                                    | The trigger(s) that would be placed (`kind`, side, prices, direction, order kind, and size for conditionals).                                                                                                                                                                        |
| cancelStopLoss, cancelOrdersById, cancelAll                            | `wouldCancel`                                          | What would be cancelled (`kind: "stopLoss"` \| `"ordersById"` \| `"all"`, plus the targets).                                                                                                                                                                                         |
| depositCollateral, withdrawCollateral                                  | `wouldMove`                                            | `{ direction: "deposit" \| "withdraw", amountUsdc }`.                                                                                                                                                                                                                                |
| transferCollateral, transferCollateralChildToParent, syncParentToChild | `wouldMove`                                            | `{ kind, srcSubaccountIndex, dstSubaccountIndex, amountUsdc? }` (no `amountUsdc` for sync).                                                                                                                                                                                          |

Example paper fill (`placeMarketOrder`, buy 10 SOL against a book whose best ask is 84.90):

```json
{
  "success": true,
  "operation": "placeMarketOrder",
  "simulated": true,
  "signature": "PAPER",
  "symbol": "SOL",
  "fill": {
    "side": "Bid",
    "requestedBaseUnits": "10",
    "filledBaseUnits": "10",
    "avgPriceUsd": "84.9",
    "notionalUsd": "849",
    "feeUsd": "0.38205",
    "partial": false,
    "priceCapped": false,
    "usedFallbackMid": false
  }
}
```

Going live: wallet activation [#going-live-wallet-activation]

Phoenix Flight is in private beta, so each wallet must be activated **once** before it can place Live trades or move collateral. Paper and read-only ops never need this, so a fresh wallet can paper-test freely.

Activation happens inline in the node config dialog, not in Connections and not on the node card:

1. Open a Phoenix node and pick a signed op (e.g. `placeLimitOrder`).
2. Flip it to **Live**. If the wallet isn't activated yet, an **Activate Phoenix Flight access** panel appears at the bottom.
3. Choose your code type (access or referral), paste the code, and click **Activate**.

You can use **either** an access code **or** a referral code; a valid referral code works on its own, you don't need an allowlist invite first. Request a code at [x.com/PhoenixTrade](https://x.com/PhoenixTrade).

Once activated, the panel disappears for good across every Phoenix node and workflow for that wallet, and live signed ops broadcast without prompting for a code again. No code is ever stored on the node, in workflow exports, or in a credential.

You also need USDC collateral on Phoenix before placing orders. Use `depositCollateral` (or fund directly at phoenix.trade); both deposit and withdraw run a balance precheck and fail fast with a clear message if you're short.

Operations [#operations]

Market data (read-only) [#market-data-read-only]

| Operation             | Description                                           | Key inputs                                              | Signs? |
| --------------------- | ----------------------------------------------------- | ------------------------------------------------------- | ------ |
| getMarkets            | List all available Phoenix perp markets               | (none)                                                  | No     |
| getMarket             | Get a single market's fees, risk, and funding cadence | symbol                                                  | No     |
| getOrderbook          | Get L2 orderbook depth                                | symbol                                                  | No     |
| getCandles            | OHLCV candle history for a market                     | symbol, **timeframe**, optional startTime/endTime/limit | No     |
| getMarketFills        | Public trade tape for a market                        | symbol, optional limit/cursor                           | No     |
| getMarketStatsHistory | Open interest, fees, mark/spot price over time        | symbol, optional timeframe/startTime/endTime/limit      | No     |
| getFundingOverview    | Funding-rate series across all markets                | optional startTime/endTime/perMarketLimit               | No     |
| getFundingRateHistory | Funding-rate series for one market                    | symbol, optional startTime/endTime/limit                | No     |

**getMarkets**: Lists every perp market live on Phoenix in a single call, each with its fee schedule, risk/leverage tiers, and funding cadence. Takes no inputs. Use it to discover what's tradable, populate a market picker, or loop a workflow over every symbol. The raw markets array comes back under `data`.

**getMarket**: The same per-market detail as one row of `getMarkets`, but scoped to a single `symbol` you already know. Cheaper than pulling the whole list when you only care about one market. Returns that market's taker/maker fees, risk parameters, and funding info under `data`, with `symbol` echoed back.

**getOrderbook**: The current L2 order-book depth for `symbol`: aggregated bid and ask levels (price + size) plus the mid price. This is the exact book the paper-trading simulator walks to estimate fills. Use it to check liquidity and spread before trading, or to feed a pricing decision into an upstream AI/logic node.

**getCandles**: OHLCV candle history for `symbol` at a **required** `timeframe` (`1m`, `5m`, `15m`, `1h`, `4h`, `1d`, etc.), with an optional `startTime`/`endTime` window and `limit`. Use it for charting, technical indicators, or backtesting logic. Times go on the wire as Unix seconds; the executor converts whatever format you supply.

**getMarketFills**: The public trade tape for `symbol`: the most recent fills that printed on the market, from all traders, newest first. Paginates by `limit`/`cursor` (no time window). Use it to watch live order flow or compute recent traded volume.

**getMarketStatsHistory**: A time series of market-level statistics for `symbol`: open interest, fees, and mark/spot price over time. Optional `timeframe`, time window, and `limit`. Use it to track how a market's open interest or basis evolves, e.g. to detect crowding.

**getFundingOverview**: Funding-rate history for **all** markets at once, returning one series per market so you don't have to loop symbol-by-symbol. Takes no `symbol`. `perMarketLimit` caps the number of points in each series (this is deliberately a different field from `limit`). Use it to scan funding across the whole venue, e.g. to find the richest carry.

**getFundingRateHistory**: Funding-rate history for a **single** `symbol` over an optional time window. Use it when you only care about one market and want finer control than the venue-wide overview gives.

Account (read-only, uses your connected wallet) [#account-read-only-uses-your-connected-wallet]

| Operation                  | Description                                                                          | Key inputs                                                | Signs? |
| -------------------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------------- | ------ |
| getTraderState             | Collateral, positions, open orders, triggers (one snapshot)                          | (none)                                                    | No     |
| getTraderOrderHistory      | Paginated order history (V2: includes intended/placed/currentState/fills aggregates) | optional symbol/limit/cursor/startTime/endTime            | No     |
| getTraderTradeHistory      | Paginated fills history (V2 endpoint)                                                | optional symbol/limit/cursor                              | No     |
| getTraderCollateralHistory | Paginated deposits/withdrawals/transfers                                             | optional limit/cursor                                     | No     |
| getTraderFundingHistory    | Paginated funding payments                                                           | optional symbol/startTime/endTime/limit/cursor/resolution | No     |
| getTraderPnl               | Realized + unrealized PnL series over time                                           | **resolution**, optional startTime/endTime/limit          | No     |
| getTraderPortfolioValues   | Portfolio value series over time                                                     | **resolution**, optional startTime/endTime/limit          | No     |
| getTraderMarketPnl         | PnL per market                                                                       | **resolution**, optional symbol/startTime/endTime/limit   | No     |
| getNotifications           | Phoenix-side fill / liquidation / SL execution notifications                         | optional limit/cursor                                     | No     |

These read your own account using the workflow owner's Privy wallet address (derived server-side, never client input). No signature and no whitelist are needed.

**getTraderState**: A single live snapshot of your Phoenix account: collateral, open positions, resting orders, and active triggers across all subaccounts. If the wallet has never opened a Phoenix account it returns `data: null` and `hasTraderAccount: false` instead of erroring, so downstream branches can test `{phoenixResponse.hasTraderAccount}` without try/catch. This is the canonical "what do I hold right now" read, and the source you pull `orderSequenceNumber` / `priceTicks` from when cancelling orders by id.

**getTraderOrderHistory**: Paginated history of your orders (the V2 endpoint), including intended vs placed vs current state and per-order fill aggregates. Optional `symbol` filter, time window, and `limit`/`cursor`. Use it to audit what you submitted and how each order resolved.

**getTraderTradeHistory**: Paginated history of your executed fills (the V2 endpoint). Paginates by `limit`/`cursor` with an optional `symbol` filter and no time window. Use it for realized-trade accounting or to compute your average entry on a position.

**getTraderCollateralHistory**: Paginated record of your collateral movements: deposits, withdrawals, and inter-subaccount transfers. `limit`/`cursor` only. Use it to reconcile balances or build a funding ledger for a strategy.

**getTraderFundingHistory**: Paginated history of the funding payments you've paid or received, with optional `symbol`, time window, and a `resolution` bucket. Use it to attribute funding cost (or income) to a position over time.

**getTraderPnl**: Your combined realized + unrealized PnL as a time series. Requires a `resolution` (bucket size like `1h` or `1d`); optional time window and `limit`. Use it to chart account performance over time.

**getTraderPortfolioValues**: Your total portfolio value (account equity) as a time series; also requires `resolution`. Like `getTraderPnl` but tracks equity rather than PnL, so it's what you'd plot as an equity curve.

**getTraderMarketPnl**: PnL broken down per market, requiring `resolution`, with optional `symbol` to scope to one market. Use it to see which markets are making or losing you money.

**getNotifications**: Phoenix-side notifications for your account: fills, liquidations, and stop-loss executions. `limit`/`cursor` only. Use it to react to events (e.g. send an alert on liquidation) without polling positions yourself.

Trading (signed) [#trading-signed]

| Operation                     | Description                                            | Key inputs                                                                | Signs? |
| ----------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------- | ------ |
| placeLimitOrder               | Resting limit order                                    | symbol, side, priceUsd, baseUnits                                         | Yes    |
| placeMarketOrder              | Take liquidity up to a price cap                       | symbol, side, priceLimitUsd, baseUnits                                    | Yes    |
| placePostOnlyOrder            | Maker-only limit (rejects on cross)                    | symbol, side, priceUsd, baseUnits                                         | Yes    |
| placeStopLoss                 | Bare stop-loss / take-profit tied to a market          | symbol, side, triggerPrice, executionPrice, executionDirection, orderKind | Yes    |
| cancelStopLoss                | Cancel an existing stop-loss / TP                      | symbol, executionDirection                                                | Yes    |
| placePositionConditionalOrder | Up to two greater/less triggers attached to a position | symbol, conditionalTriggers (JSON), optional baseUnits                    | Yes    |
| cancelOrdersById              | Cancel specific resting orders                         | symbol, orderIds                                                          | Yes    |
| cancelAll                     | Cancel every resting order on the symbol               | symbol                                                                    | Yes    |

**placeLimitOrder**: Posts a standard limit order from your `side` (Bid = buy/long, Ask = sell/short), at `priceUsd`, for `baseUnits` of the base asset. It immediately fills against any resting orders priced better than yours, then rests the remainder at your price as a maker. This is the default order type for most strategies. In paper mode the fill is estimated from the live book and any unfilled remainder is reported under `wouldRest`.

**placeMarketOrder**: Takes liquidity immediately, walking the book from the best price until your `baseUnits` is filled, but never past the **required** `priceLimitUsd` worst-price cap. The cap is mandatory: the node refuses to submit without it, so a thin or fast-moving book can't fill you at a runaway price. Use it when getting filled matters more than the spread you pay.

**placePostOnlyOrder**: A maker-only limit order. Same inputs as `placeLimitOrder`, but it will **never** take liquidity: if your price would cross the spread (a buy at or above the best ask, a sell at or below the best bid) Phoenix rejects it on-chain instead of filling. Use it to guarantee you post as a maker and never pay taker fees. In paper mode a crossing post-only fails the node, exactly mirroring the live rejection, so you don't get a false "resting" result.

**placeStopLoss**: Places a standalone stop-loss or take-profit tied to a `symbol` (not attached to a specific order). `triggerPrice` arms it; `executionDirection` (`GreaterThan` / `LessThan`) sets whether it fires when price rises above or falls below the trigger; when it fires it submits at `executionPrice` as either `IOC` (fill what it can immediately at that worst-case price, cancel the rest) or `Limit` (rest at that price as a maker). `side` is the direction of the protective order. Use it to auto-exit a position at a threshold.

**cancelStopLoss**: Removes an existing stop-loss / take-profit on `symbol`, identified by the `executionDirection` you placed it with. Use it to clear a trigger before replacing it, or once the position it protected is closed.

**placePositionConditionalOrder**: Attaches up to two price triggers to a position in one instruction via the `conditionalTriggers` JSON: a `greater` bucket (fires when price rises through a level) and/or a `less` bucket (fires when price falls through one). Each entry carries its own `tradeSide`, `orderKind` (`IOC`/`Limit`), `triggerPrice`, and `executionPrice`. Optional `baseUnits` sizes the order; omit it to size to the full position. Use it for bracket-style exits, e.g. a take-profit above plus a stop below, set together.

**cancelOrdersById**: Cancels specific resting orders identified by `orderIds`, a JSON array of `{ price | priceTicks, orderSequenceNumber }` entries pulled from a prior `getTraderState` snapshot. Use it for surgical cancellation when you don't want to wipe the whole book. See [Cancel-by-id price precision](#cancel-by-id-price-precision) for when to use `price` vs the tick-perfect `priceTicks`.

**cancelAll**: Cancels every resting order you have on `symbol` in a single instruction. Use it as a fast reset before re-quoting, or as a kill-switch step in a rebalance flow.

Collateral & subaccount management (signed) [#collateral--subaccount-management-signed]

| Operation                       | Description                                                    | Key inputs                                         | Signs? |
| ------------------------------- | -------------------------------------------------------------- | -------------------------------------------------- | ------ |
| depositCollateral               | Deposit USDC into your Phoenix trader account                  | amountUsdc                                         | Yes    |
| withdrawCollateral              | Withdraw USDC from your Phoenix trader account                 | amountUsdc                                         | Yes    |
| transferCollateral              | Move USDC between two subaccounts under the same wallet        | srcSubaccountIndex, dstSubaccountIndex, amountUsdc | Yes    |
| transferCollateralChildToParent | Sweep an isolated subaccount's full balance back to cross main | traderSubaccountIndex (the child)                  | Yes    |
| syncParentToChild               | Refresh state on an isolated subaccount from the parent        | traderSubaccountIndex (the child)                  | Yes    |

No Flight builder fee applies to any collateral op, even live: moving USDC never has Solaris AI take a cut.

**depositCollateral**: Moves `amountUsdc` of USDC from your wallet into your Phoenix trader account at the resolved `(marginType, traderSubaccountIndex)`. Before broadcasting, a precheck reads your wallet's USDC balance and fails fast (reporting actual vs required) if you're short. The first deposit to a fresh subaccount auto-registers it. You need collateral here before you can place any orders.

**withdrawCollateral**: Pulls `amountUsdc` of USDC out of your Phoenix account back to your wallet. A precheck reads `effectiveCollateralForWithdrawals` (your withdrawable balance after open positions, unrealized PnL, funding, and margin) and refuses if it's below the request, so you can branch on that error instead of hitting a cryptic on-chain failure. Use it to take profit or free up capital.

**transferCollateral**: Moves `amountUsdc` between two subaccounts under the **same** wallet, from `srcSubaccountIndex` to `dstSubaccountIndex` (both 0-100, must differ). The source must already hold collateral; a missing destination is auto-registered (Cross for subaccount 0, Isolated otherwise). Use it to rebalance capital between strategies without round-tripping through your wallet.

**transferCollateralChildToParent**: Sweeps an Isolated subaccount's **entire** balance back to the Cross main account (subaccount 0); `traderSubaccountIndex` is the child being emptied. Use it to wind down an isolated strategy and reclaim its collateral. If the parent (subaccount 0) doesn't exist yet, it's auto-registered as part of the same transaction.

**syncParentToChild**: Refreshes an Isolated child subaccount's view of state from the Cross parent; `traderSubaccountIndex` is the child. It's idempotent (re-syncing identical state is a no-op revert). Use it to reconcile a child after parent-side changes.

The &#x2A;*Signs?** column above describes **Live** mode. In **Paper** mode (the default for every op marked "Yes") nothing is signed or broadcast; the op is simulated instead.

All trading + collateral signed operations (except transfer/sync, which have their own subaccount routing) also accept `marginType` (default `Cross`) and, for Isolated margin, `traderSubaccountIndex`. See [Margin and subaccount routing](#margin-and-subaccount-routing).

Configuration [#configuration]

| Field                                  | Type                        | Required for                                                                                                                                                                                                          | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| -------------------------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| symbol                                 | string                      | `getMarket`, `getOrderbook`, and all trading / cancel ops                                                                                                                                                             | Base asset symbol (e.g., `SOL`, `BTC`, `ETH`). A legacy `-PERP` suffix is stripped automatically for backwards compatibility. Also accepted as an **optional** market filter on `getTraderOrderHistory`, `getTraderTradeHistory`, `getTraderFundingHistory`, and `getTraderMarketPnl` (leave blank for all markets). Not used by `getMarkets`, `getTraderState`, `depositCollateral`, or `withdrawCollateral`.                                                                                                                                                                                                                                                                                                                                                                                             |
| side                                   | `Bid` \| `Ask`              | placeLimitOrder, placeMarketOrder, placePostOnlyOrder, placeStopLoss                                                                                                                                                  | `Bid` = buy / long, `Ask` = sell / short. Runtime also accepts templated/imported aliases `buy` / `long` and `sell` / `short`. (`placePositionConditionalOrder` instead sets `tradeSide` per entry inside `conditionalTriggers`.)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| priceUsd                               | string                      | placeLimitOrder, placePostOnlyOrder                                                                                                                                                                                   | Limit price in USD (e.g. `135.87`). For post-only, this is the maker price the order rests at; it rejects rather than crossing if it would match a resting order on the opposite side.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| priceLimitUsd                          | string                      | placeMarketOrder                                                                                                                                                                                                      | **Required price cap.** Worst price you accept; market orders stop matching past this. Prevents slippage on thin books.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| baseUnits                              | string                      | placeLimitOrder, placeMarketOrder, placePostOnlyOrder (optional for placePositionConditionalOrder)                                                                                                                    | Order size in the base asset (e.g. `0.25` for a 0.25 SOL position). Required for the three plain place ops; on `placePositionConditionalOrder` it's optional, omit it to size to the full position.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| orderIds                               | JSON string                 | cancelOrdersById                                                                                                                                                                                                      | Array of `{ "price": <decimal USD>, "orderSequenceNumber": "<string>" }`, e.g. `[{"price": 135.87, "orderSequenceNumber": "1234"}]`. `price` may be a JSON number or a base-10 decimal string (`"135.87"`); scientific notation and hex strings are rejected so a mis-typed `"1e2"` can't silently target tick 100. Pull from a prior `getTraderState` at `data.snapshot.subaccounts[].orders[].orders[]` (each row exposes `priceUsd` and `orderSequenceNumber`). &#x2A;*Precision option:** each entry may use `priceTicks` (positive integer **string**, e.g. `"12345"`) instead of `price`. Pull from the snapshot's `priceTicks` field. Setting both `price` AND `priceTicks` on the same entry is rejected. See [Cancel-by-id price precision](#cancel-by-id-price-precision) for when to use which. |
| amountUsdc                             | string                      | depositCollateral, withdrawCollateral, transferCollateral                                                                                                                                                             | USDC amount as a base-10 decimal with at most 6 fractional digits (e.g. `"1.5"`, `"100"`, `"0.000001"`). `"1.5"` means 1.5 USDC (=1,500,000 base units), not 1,500,000 USDC. Scientific notation, hex, leading dots, and negatives are rejected; the parser converts string to bigint with no `Number()` so there's no float drift on large amounts. Supports template variables.                                                                                                                                                                                                                                                                                                                                                                                                                          |
| marginType                             | `Cross` \| `Isolated`       | Trading / cancel / deposit / withdraw signed ops (not transfer/sync)                                                                                                                                                  | Margin mode for the trader account this op targets. Defaults to `Cross`. &#x2A;*Locks irreversibly at first signed op for the (subaccount) pair.** Phoenix doesn't allow changing margin type on an existing on-chain trader account. `Cross` is always routed to subaccount 0; `Isolated` requires `traderSubaccountIndex` in `[1, 100]`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| traderSubaccountIndex                  | number (1-100)              | Trading / cancel / deposit / withdraw with Isolated margin; ALSO transferCollateralChildToParent and syncParentToChild (as the child subaccount)                                                                      | Subaccount slot. For single-subaccount ops: required when `marginType: "Isolated"`, must be omitted or 0 when `Cross`. For childToParent / sync: this IS the child subaccount, range 1-100. Subaccount 0 is reserved for Cross by SDK contract. Supports template variables. Phoenix supports up to 101 trader accounts per wallet: Cross at subaccount 0 plus Isolated at any of 1-100.                                                                                                                                                                                                                                                                                                                                                                                                                   |
| timeframe                              | string                      | `getCandles` (required), `getMarketStatsHistory` (optional)                                                                                                                                                           | Candle / stats resolution like `1m`, `5m`, `15m`, `1h`, `4h`, `1d`. Forwarded verbatim to Phoenix; whatever the upstream API accepts works.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| startTime, endTime                     | string                      | `getCandles`, `getMarketStatsHistory`, `getFundingOverview`, `getFundingRateHistory`, `getTraderOrderHistory`, `getTraderFundingHistory`, `getTraderPnl`, `getTraderPortfolioValues`, `getTraderMarketPnl` (optional) | ISO-8601, Unix seconds (10 digits), or Unix millis (13 digits). The executor normalizes per-op. Not accepted by `getMarketFills`, `getTraderTradeHistory`, `getTraderCollateralHistory`, or `getNotifications` (those endpoints paginate by `limit` / `cursor` only).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| limit                                  | number/string               | All paginated read ops (optional)                                                                                                                                                                                     | Max items per page. Phoenix enforces per-endpoint caps server-side.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| cursor                                 | string                      | `getMarketFills`, `getTraderOrderHistory`, `getTraderTradeHistory`, `getTraderCollateralHistory`, `getTraderFundingHistory`, `getNotifications` (optional)                                                            | Pagination cursor from a prior response's `nextCursor`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| perMarketLimit                         | number/string               | `getFundingOverview` (optional)                                                                                                                                                                                       | Caps points per series (overview returns one series per market). Distinct from `limit` because `FundingOverviewRequest` uses a different key.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| resolution                             | string                      | `getTraderPnl`, `getTraderPortfolioValues`, `getTraderMarketPnl` (required), `getTraderFundingHistory` (optional)                                                                                                     | Aggregation bucket for time-series outputs (e.g. `1h`, `1d`). PnL / portfolio endpoints reject empty resolution.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| triggerPrice, executionPrice           | string (USD decimal)        | `placeStopLoss`                                                                                                                                                                                                       | Trigger price fires the SL; execution price is the resting limit (Limit kind) or worst acceptable fill (IOC kind). Converted to ticks per the market's tick size.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| executionDirection                     | `GreaterThan` \| `LessThan` | `placeStopLoss`, `cancelStopLoss`                                                                                                                                                                                     | Trigger comparison direction. Runtime also accepts aliases `above`/`below`/`gt`/`lt`/`>`/`<` to play nicely with template-driven AI workflows.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| orderKind                              | `IOC` \| `Limit`            | `placeStopLoss`                                                                                                                                                                                                       | `IOC` cancels any unfilled remainder immediately at the execution price (worst case); `Limit` rests at the execution price as a maker order.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| conditionalTriggers                    | JSON string                 | `placePositionConditionalOrder`                                                                                                                                                                                       | Array of 1-2 entries `{"bucket":"greater"\|"less", "tradeSide":"Bid"\|"Ask", "orderKind":"IOC"\|"Limit", "triggerPrice":"<USD>", "executionPrice":"<USD>"}`. At most one per bucket; setting `bucket: "greater"` twice is rejected.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| srcSubaccountIndex, dstSubaccountIndex | number (0-100)              | `transferCollateral`                                                                                                                                                                                                  | Source and destination subaccounts. Both required, must differ, both in \[0, 100]. Source must already hold collateral; destination is auto-registered (Isolated for non-zero, Cross for 0) if it doesn't exist yet.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |

Invite codes are never configured as fields on the Phoenix node. Activation is a one-time wallet-level action handled via the inline panel described in [Going live](#going-live-wallet-activation).

Using template variables [#using-template-variables]

Most fields accept template variables (e.g. `{aiResponse.data.targetPrice}`): `symbol`, `side`, `priceUsd`, `priceLimitUsd`, `baseUnits`, `orderIds`, `amountUsdc`, `marginType`, `traderSubaccountIndex`, `timeframe`, `startTime`, `endTime`, `limit`, `cursor`, `perMarketLimit`, `resolution`, `triggerPrice`, `executionPrice`, `executionDirection`, `orderKind`, `conditionalTriggers`, `srcSubaccountIndex`, and `dstSubaccountIndex`.

A few of these (`side`, `marginType`, `orderKind`, `executionDirection`) are shown as fixed dropdowns in the canvas config rather than free-text boxes, so on the canvas you pick a value. To drive one from an upstream node, template it from a JSON-edited workflow instead (e.g. `side` as `{aiResponse.suggestedSide}` resolving to `"Bid"` or `"Ask"`).

Numeric fields (`priceUsd`, `priceLimitUsd`, `baseUnits`) must be base-10 decimal strings (e.g. `"135.87"`, `"0.25"`); scientific notation, hex, and leading dots are rejected. `amountUsdc` follows the same shape but caps fractional digits at 6 (USDC's on-chain decimal precision).

Time window inputs [#time-window-inputs]

Read ops that take a time window (`getCandles`, `getMarketStatsHistory`, `getFundingOverview`, `getFundingRateHistory`, `getTraderOrderHistory`, `getTraderFundingHistory`, `getTraderPnl`, `getTraderPortfolioValues`, `getTraderMarketPnl`) accept any of these formats for `startTime` / `endTime`:

* ISO-8601 string, e.g. `2026-01-01T00:00:00Z`
* Unix seconds, 10-digit integer string, e.g. `1700000000`
* Unix millis, 13-digit integer string, e.g. `1700000000000` (floored to seconds)

The executor normalizes the format per endpoint, so you don't have to remember which one each uses. `getMarketFills`, `getTraderTradeHistory`, `getTraderCollateralHistory`, and `getNotifications` paginate by `limit` / `cursor` only and expose no time window.

Pagination [#pagination]

For ops with a `cursor` field, Phoenix returns `nextCursor` on each page. Template the value back in via `{phoenixResponse.data.nextCursor}` and re-run the node to fetch the next page. `cursor` is empty/absent on the first call. Same field name and shape across `getMarketFills`, `getTraderOrderHistory`, `getTraderTradeHistory`, `getTraderCollateralHistory`, `getTraderFundingHistory`, and `getNotifications`.

Output [#output]

Symbol-scoped market reads (`getMarket`, `getOrderbook`, `getCandles`, `getMarketFills`, `getMarketStatsHistory`, `getFundingRateHistory`) echo `symbol`; `getMarkets` and `getFundingOverview` omit it (they take no symbol):

```json
{
  "success": true,
  "operation": "getOrderbook",
  "symbol": "SOL",
  "data": { /* raw Phoenix response */ }
}
```

`getTraderState` adds the wallet authority and `hasTraderAccount`:

```json
{
  "success": true,
  "operation": "getTraderState",
  "authority": "7abc...",
  "data": null,
  "hasTraderAccount": false
}
```

Live signed place/cancel ops return a real signature plus status flags (in Paper mode the shape is different: see [Simulated outputs](#simulated-outputs)):

```json
{
  "success": true,
  "operation": "placeLimitOrder",
  "symbol": "SOL",
  "signature": "5JR...",
  "registeredThisCall": false,
  "nowWhitelisted": true
}
```

* `registeredThisCall: true` means this broadcast bundled the register-trader instruction (the first signed op on a fresh subaccount). It's distinct from `getTraderState.hasTraderAccount`, which describes current on-chain state independent of this call.
* `nowWhitelisted: true` is always true on success. Useful as a downstream gate like `{phoenixResponse.nowWhitelisted}`. Reference any field with the response name (default `phoenixResponse`), e.g. `{phoenixResponse.signature}` to forward the tx hash to a notification node.

Collateral ops return the same shape with `amountUsdc` in place of `symbol`. `transferCollateral` echoes both subaccount indices; `transferCollateralChildToParent` and `syncParentToChild` echo `childSubaccountIndex`.

Account-history reads use a nested envelope: outer `data` is our wrapper, inner `data` is Phoenix's paginated response, so you template the next page as `{phoenixResponse.data.nextCursor}`:

```json
{
  "success": true,
  "operation": "getTraderOrderHistory",
  "authority": "7abc...",
  "data": { "data": [/* orders */], "nextCursor": "...", "hasMore": true }
}
```

Margin and subaccount routing [#margin-and-subaccount-routing]

Phoenix supports two margin modes and up to 101 trader accounts per wallet:

* **Cross** is always routed to subaccount 0 (one shared collateral pool backs every position in that subaccount). Picking `marginType: "Cross"` and omitting `traderSubaccountIndex` covers the typical single-strategy case.
* **Isolated** silos each strategy in its own subaccount (1-100), with its own collateral pool, positions, and margin accounting. Pick `marginType: "Isolated"` and set `traderSubaccountIndex` to any number in 1-100. Each `(margin, subaccount)` combo is its own on-chain account that auto-registers on its first signed op.

You can mix them on the same wallet, e.g. a Cross BTC strategy on subaccount 0 plus an Isolated SOL trial on subaccount 5. Cross at a non-zero subaccount, or Isolated at subaccount 0, are rejected at publish time. Margin mode locks irreversibly at the first signed op for a subaccount: once registered as Cross or Isolated it stays that way, so switch by routing the next strategy to a different subaccount.

`depositCollateral` and `withdrawCollateral` target the resolved `(margin, subaccount)` pair. To fund an Isolated strategy on subaccount 5, set `marginType: "Isolated", traderSubaccountIndex: 5` on the deposit node. The first deposit auto-registers subaccount 5 as Isolated and credits the USDC there, independent of subaccount 0.

Cancel-by-id price precision [#cancel-by-id-price-precision]

When cancelling specific orders, you identify each one by price. Phoenix stores prices as integer "ticks," and converting a decimal USD price back to a tick uses floor math that can occasionally land on the tick next to your actual order, so the cancel misses. You have two ways to cancel:

* **`price` (USD)**: the simple path. Hand the node a decimal USD value (e.g. `135.87`). Works cleanly for prices that aren't on a precision-fragile boundary.
* **`priceTicks` (string)**: the precision-safe path. Pull the order's exact on-chain tick from a `getTraderState` snapshot at `data.snapshot.subaccounts[].orders[].orders[].priceTicks`. The node converts it back to a price that is guaranteed to target the right tick, and refuses to submit if it can't, so you never cancel the wrong order.

Pick `priceTicks` for deterministic, tick-perfect cancellation (high-frequency or replace-on-cancel flows). Pick `price` for the simpler path when the order's price is whole-cents-flat. Each entry must use one or the other; setting both is rejected.

Common use cases [#common-use-cases]

* AI sentiment to a conditional `placeLimitOrder` on Phoenix
* Cross-venue arbitrage: read Drift orderbook, place a hedge on Phoenix
* Scheduled rebalancing: `getTraderState` to `cancelAll` to fresh limit orders
* Scheduled collateral top-up: cron trigger to `depositCollateral` with a templated `amountUsdc`
* Automated profit-take: run `withdrawCollateral` with your threshold amount and let the precheck gate it. The precheck reads `effectiveCollateralForWithdrawals` (which accounts for open positions, unrealized PnL, funding, and margin) and rejects with a clean "insufficient withdrawable" error, so you can route the workflow on that error. Note it's best-effort: state can move between precheck and broadcast, so a precheck-pass withdraw can still fail on-chain. Don't gate on `getTraderState.data.snapshot.subaccounts[].collateral`: it's raw per-subaccount collateral and does NOT account for positions/PnL/funding/margin.

Under the hood [#under-the-hood]

You don't need any of this to use the node. It's here for the curious and for anyone debugging unexpected behavior.

Builder fees [#builder-fees]

Solaris AI is a registered Phoenix Flight builder, so order-placing instructions (`placeLimitOrder`, `placeMarketOrder`, `placePostOnlyOrder`, `placePositionConditionalOrder`) carry Solaris AI's builder code and earn a builder fee on the resulting **taker** fills (rate set server-side by Phoenix, currently 1 bp). Maker fills earn no builder fee, so a post-only order (maker-only) never generates one even though its instruction is still wrapped. Stop-loss, cancel, and collateral instructions are not order-placing, so they're never wrapped and never carry a fee. A test asserts the builder authority stays wired up so a future refactor can't silently drop it.

Reliability [#reliability]

* `placeMarketOrder` will not submit without `priceLimitUsd` set, even though the SDK accepts uncapped market packets. The node treats that as user error and fails before broadcasting.
* Each signed transaction is prepended with compute-budget instructions and a priority fee. The fee is estimated per broadcast from a live Helius `getPriorityFeeEstimate` (High level, keyed off the transaction's writable accounts) and clamped to a floor/ceiling (currently 40,000 to 1,000,000 micro-lamports/CU), so time-sensitive trades and cancels stay competitive under congestion without a fee spike draining the wallet. Estimating per build means a blockhash-expiry retry also re-prices instead of resubmitting the same fee. If the estimate is unavailable (RPC without the method, timeout, error), it falls back to the floor. Most ops use a 400k CU budget; deposit and withdraw chain more instructions and use 800k.
* If a Live signed op throws, the engine does **not** automatically re-run the node, because a fresh attempt could duplicate an order or fund movement that may have actually landed (a thrown op is ambiguous about whether its transaction landed). The one exception is `syncParentToChild`, which is idempotent (re-syncing identical state is a no-op) and stays retryable. Paper ops are always retryable since they never broadcast; a transient order-book read just runs again.
* Deposits and withdrawals run a balance precheck (wallet USDC for deposit, `effectiveCollateralForWithdrawals` for withdraw) and fail fast with a clear message rather than a cryptic on-chain error. The withdraw precheck is best-effort: on-chain state can move between precheck and broadcast.

How activation is checked [#how-activation-is-checked]

Activation is verified, never stored. The activation panel calls a server action that resolves your wallet via Privy, checks Phoenix's whitelist, and only consumes a code if you actually need one (an already-whitelisted wallet, or one that already has a Phoenix trader account from a prior referral activation, short-circuits without burning a code). At runtime the executor only **reads** activation state before a Live broadcast: it checks the whitelist, falls back to probing for an existing default trader account, and throws a clear "open the Phoenix node and click Activate" error if neither proves access. No transaction is broadcast in that case, and the executor never sees invite codes.

SDK coverage [#sdk-coverage]

The node wraps the Ellipsis Labs Rise SDK. These SDK surfaces are intentionally **not** exposed as node operations:

* `placeMultiLimitOrder`, `placeAttachedConditionalOrder`, `placeLimitOrderWithConditionals`, `cancelUpTo`, `cancelConditionalOrder`: only practical via low-level instruction builders that need manual account resolution.
* `placeIsolatedLimitOrder` / `placeIsolatedMarketOrder`: superseded by our two-step `transferCollateral` then `placeLimitOrder` flow, which has clearer node-level intent.
* `delegateTrader`: multi-signer / copy-trading delegation, out of scope for typical workflows.
* Escrow operations (create / accept / cancel request, grant / revoke permission): niche inter-trader transfers, not in current scope.
* Notification ack endpoints: write ops on the notification feed; would need their own signing-op slot.
* WebSocket / streaming surfaces: workflows run as one-shot HTTP calls, so long-lived subscriptions don't fit.

Next steps [#next-steps]

* [Drift](/docs/nodes/defi/drift) - compare perps surface
* [Jupiter](/docs/nodes/defi/jupiter) - spot routing for hedging
