> ## Documentation Index
> Fetch the complete documentation index at: https://docs.phoenix.trade/llms.txt
> Use this file to discover all available pages before exploring further.

# Accounts

> Manage Phoenix trader account indexes, cross-margin accounts, isolated subaccounts, and trader-state subscriptions with the Rise SDK.

This page covers the trader account model after a trader is eligible to use Phoenix. To activate a trader account, see [Trader Onboarding](/sdk/register).

## Trader account indexes

A Phoenix trader account is a PDA derived from the authority wallet, `pda_index`, `subaccount_index`, and the Phoenix program id. In the TypeScript SDK these are passed as `traderPdaIndex` and `traderSubaccountIndex`; in Rust, `TraderKey::new(...)` uses `(pda_index = 0, subaccount_index = 0)` and `TraderKey::new_with_idx(...)` lets you specify both indexes.

Use `pda_index = 0` for user accounts. With exchange gating enabled, only `pda_index = 0` can be activated; a non-zero PDA index cannot be activated. In the future this will be used to support multiple portfolios.

* `subaccount_index = 0` is the cross-margin account. It is created during onboarding with `max_positions` between `32` and `128` inclusive; the default is `128`.
* `subaccount_index > 0` is an isolated account. Isolated accounts have `max_positions = 1`, so each account is intended for one isolated position.

## Cross vs isolated

Cross margin and isolated margin are represented as separate trader subaccounts under the same authority and `pda_index`.

| Account          | Index                  | Created by                                       | Intended use                                   | State stream behavior                                 |
| ---------------- | ---------------------- | ------------------------------------------------ | ---------------------------------------------- | ----------------------------------------------------- |
| Cross account    | `subaccount_index = 0` | Trader onboarding                                | Shared collateral and many positions           | Included in the trader-state snapshot and deltas      |
| Isolated account | `subaccount_index > 0` | Register child subaccount, then sync from parent | One isolated position with separate collateral | Included in the same trader-state snapshot and deltas |

Subscribe to trader state once per authority and `traderPdaIndex`. The stream returns the cross account and every registered isolated subaccount in the same snapshot, then sends deltas keyed by `subaccountIndex`.

## Trader state across subaccounts

Use the SDK state primitives instead of applying raw snapshot and delta messages yourself. In TypeScript, `createTraderStateStore(client)` exposes `subaccountIndices()`, `subaccount(index)`, `position(index, symbol)`, `orders(index, symbol)`, `triggers(index, symbol)`, and `marginInputs()` across all subaccounts. In Rust, `Trader::apply_update(&msg)` maintains a `Trader` container whose `subaccounts` map includes cross and isolated accounts.

<CodeGroup>
  ```ts TypeScript theme={null}
  import { createPhoenixClient, createTraderStateStore } from "@ellipsis-labs/rise";

  const client = createPhoenixClient({
    apiUrl: "https://perp-api.phoenix.trade",
    rpcUrl: "https://api.mainnet-beta.solana.com",
    ws: { connectMode: "eager" },
    exchangeMetadata: { stream: true },
  });

  const traderState = createTraderStateStore(client);
  const resource = traderState.resource({
    authority: "AUTHORITY_PUBKEY",
    traderPdaIndex: 0,
  });

  const release = resource.retain();
  const snapshot = await resource.ready();

  for (const subaccount of snapshot?.subaccounts ?? []) {
    console.log(subaccount.subaccountIndex, subaccount.collateral);
  }

  const unsubscribe = resource.subscribe((state, previous) => {
    if (state.snapshot === previous.snapshot) return;

    for (const subaccountIndex of resource.subaccountIndices()) {
      const subaccount = resource.subaccount(subaccountIndex);
      console.log({
        subaccountIndex,
        collateral: subaccount?.collateral,
        positions: subaccount?.positionSymbols,
        orderSymbols: subaccount?.orderSymbols,
      });
    }
  });

  // Later, when this component/service no longer needs live state:
  unsubscribe();
  release();
  ```

  ```rust Rust theme={null}
  use phoenix_rise::api::{PhoenixWSClient, Trader, TraderKey};
  use solana_pubkey::Pubkey;

  let authority: Pubkey = "AUTHORITY_PUBKEY".parse()?;
  let key = TraderKey::from_authority(authority);
  let mut trader = Trader::new(key.clone());

  let ws = PhoenixWSClient::new_from_env()?;
  let (mut rx, _handle) = ws.subscribe_to_trader_state(&authority)?;

  while let Some(msg) = rx.recv().await {
      trader.apply_update(&msg);

      for (subaccount_index, subaccount) in &trader.subaccounts {
          println!(
              "subaccount={} collateral={} positions={} orders={}",
              subaccount_index,
              subaccount.collateral,
              subaccount.positions.len(),
              subaccount.orders.len()
          );
      }
  }
  ```
</CodeGroup>

References:

* [TypeScript trader-state store example](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/examples/04-trader-state-store.ts)
* [Rust trader-state example](https://github.com/Ellipsis-Labs/rise-public/blob/master/rust/sdk/examples/subscribe_trader_state.rs)

## Set position authority

Use the on-chain `DelegateTrader` instruction to set or replace the `position_authority` for a trader account. The current account `authority` signs the transaction; the new position authority is recorded on the trader account but does not need to sign the delegation transaction.

<Tip>
  Set `position_authority` to an embedded wallet, such as a Privy wallet, when you want your app to support one-click transaction signing for trading actions while keeping collateral withdrawals controlled by the user's authority wallet.
</Tip>

The SDK parameter `traderPdaIndex` corresponds to the account's `portfolio_index`, and `traderSubaccountIndex` corresponds to `subaccount_index`.

```ts TypeScript theme={null}
import { createPhoenixClient } from "@ellipsis-labs/rise";

const client = createPhoenixClient({
  apiUrl: "https://perp-api.phoenix.trade",
  rpcUrl: "https://api.mainnet-beta.solana.com",
  exchangeMetadata: { stream: false },
});

const authority = "PHANTOM_WALLET_PUBKEY";
const newPositionAuthority = "PRIVY_WALLET_PUBKEY";

const delegateTraderIx = await client.ixs.buildDelegateTrader({
  traderWallet: authority,
  traderPdaIndex: 0,
  traderSubaccountIndex: 0,
  newPositionAuthority,
});

// Send this instruction in a transaction signed by `authority`.
```

`DelegateTrader` updates one trader account. If you use isolated accounts, each isolated trader account has its own stored `position_authority`. Syncing a child isolated account from its parent cross account copies the parent's current `position_authority` to the child.

### Send market orders with position authority

When a trader account has a separate `position_authority`, build order instructions with the trader account's original `authority`, but make the position authority the signing wallet.

| SDK        | Trader account owner                                 | Market-order signer                                              |
| ---------- | ---------------------------------------------------- | ---------------------------------------------------------------- |
| TypeScript | Pass `authority` as the trader account authority     | Pass `positionAuthority` separately                              |
| Rust       | Derive `TraderKey` from the trader account authority | Pass `position_authority` to `MarketOrderTicket::authority(...)` |

<CodeGroup>
  ```ts TypeScript theme={null}
  import { Side } from "@ellipsis-labs/rise";

  const authority = "PHANTOM_WALLET_PUBKEY";
  const positionAuthority = "PRIVY_WALLET_PUBKEY";
  const symbol = "SOL";

  const marketPacket = await client.orderPackets.buildMarketOrderPacket({
    symbol,
    side: Side.Bid,
    baseUnits: "0.25",
  });

  const placeMarketIx = await client.ixs.placeMarketOrder({
    authority,
    positionAuthority,
    symbol,
    orderPacket: marketPacket,
  });

  // Send the transaction with `positionAuthority` as the signer.
  ```

  ```rust Rust theme={null}
  use phoenix_rise::{MarketOrderTicket, PhoenixTxBuilder, Side, TraderKey};

  let authority = phantom_wallet_pubkey;
  let position_authority = privy_wallet_pubkey;
  let trader = TraderKey::new(authority);
  let builder = PhoenixTxBuilder::new(&metadata);

  let market_ixs = builder
      .place_market_order(
          MarketOrderTicket::builder()
              .authority(position_authority)
              .trader_account(trader.pda())
              .symbol("SOL")
              .side(Side::Bid)
              .num_base_lots(25_000)
              .build()?,
      )
      .await?;

  // Send the transaction with `position_authority` as the signer.
  ```
</CodeGroup>

In TypeScript, `authority` is still used to resolve the trader PDA, and `positionAuthority` becomes the signer account in the Phoenix instruction. In Rust, `TraderKey::new(authority)` still resolves the trader PDA from the original wallet authority, but the ticket's `.authority(position_authority)` is the signer placed into the market-order instruction.

<Note>
  Do not derive a new trader account from the position authority. The trader account remains the PDA for the user's authority wallet; only the signer changes.
</Note>

## Create isolated accounts

Onboarding creates the user's cross-margin trader account at `traderPdaIndex = 0` and `traderSubaccountIndex = 0`. After that cross account exists, create isolated accounts by registering a non-zero subaccount index under `traderPdaIndex = 0`, then syncing the parent cross account to the child isolated account.

The sync instruction copies the parent account's current capabilities and fee configuration to the isolated account.

<CodeGroup>
  ```ts TypeScript theme={null}
  import { MarginType, createPhoenixClient } from "@ellipsis-labs/rise";

  const client = createPhoenixClient({
    apiUrl: "https://perp-api.phoenix.trade",
    rpcUrl: "https://api.mainnet-beta.solana.com",
    exchangeMetadata: { stream: false },
  });

  const authority = "AUTHORITY_PUBKEY";
  const traderPdaIndex = 0;
  const isolatedSubaccountIndex = 1;

  const registerIsolatedIx = await client.ixs.buildRegisterTrader({
    authority,
    marginType: MarginType.Isolated,
    traderPdaIndex,
    traderSubaccountIndex: isolatedSubaccountIndex,
  });

  const syncIsolatedIx = await client.ixs.buildSyncParentToChild({
    traderWallet: authority,
    traderPdaIndex,
    traderSubaccountIndex: isolatedSubaccountIndex,
  });

  const instructions = [registerIsolatedIx, syncIsolatedIx];

  // Send the instructions in a transaction signed by `authority`.
  // The parent cross account must already exist.
  ```

  ```rust Rust theme={null}
  use phoenix_rise::{PhoenixHttpClient, PhoenixMetadata, PhoenixTxBuilder, TraderKey};
  use solana_keypair::read_keypair_file;
  use solana_signer::Signer;

  let keypair = read_keypair_file("PATH_TO_KEYPAIR.json")?;
  let parent_key = TraderKey::new(keypair.pubkey()); // pda_index 0, subaccount_index 0
  let child_key = TraderKey::new_with_idx(parent_key.authority(), 0, 1);

  let http = PhoenixHttpClient::new_from_env()?;
  let exchange = http.get_exchange().await?.into();
  let metadata = PhoenixMetadata::new(exchange);
  let builder = PhoenixTxBuilder::new(&metadata);

  let mut instructions = builder.build_register_trader(
      child_key.authority(),
      child_key.pda_index,
      child_key.subaccount_index,
  )?;
  instructions.extend(builder.build_sync_parent_to_child(
      child_key.authority(),
      parent_key.pda(),
      child_key.pda(),
  )?);

  // Send the instructions in a transaction signed by `authority`.
  // The parent cross account must already exist.
  ```
</CodeGroup>

<Note>
  Use `traderSubaccountIndex = 0` only for the cross account. Isolated accounts use non-zero subaccount indexes.
</Note>

## Fund isolated accounts and open positions

When you open an isolated position yourself, mirror the same instruction order used by the server-built isolated order routes:

1. Pick a non-zero isolated subaccount index.
2. If that child account does not exist, register it.
3. Sync the parent cross account to the child account.
4. Transfer collateral from the parent cross account to the child account with `TransferCollateral`.
5. Place the order on the child account.
6. Optionally append `TransferCollateralChildToParent` to sweep child collateral back to the parent when the child has no active limit orders or positions, including when the order you just placed closes the isolated position.

<Note>
  The parent cross account is subaccount `0`. `TransferCollateral` moves a specific amount between two trader accounts; use `srcSubaccountIndex = 0` and `dstSubaccountIndex = childSubaccountIndex` to fund an isolated child. TypeScript amounts are native USDC units, so `1 USDC = 1_000_000n`. The Rust builder accepts decimal USDC amounts.
</Note>

<Note>
  Unused isolated collateral does not stay parked forever. The server-built isolated order routes append `TransferCollateralChildToParent` by default, which sweeps collateral back to cross margin in the same transaction if the order closes the position and leaves no active limit orders. An off-chain crank can also sweep idle isolated collateral when the subaccount has no active limit orders or positions.
</Note>

<CodeGroup>
  ```ts TypeScript theme={null}
  import {
    MarginType,
    OrderFlags,
    SelfTradeBehavior,
    Side,
    createPhoenixClient,
  } from "@ellipsis-labs/rise";

  const client = createPhoenixClient({
    apiUrl: "https://perp-api.phoenix.trade",
    rpcUrl: "https://api.mainnet-beta.solana.com",
    exchangeMetadata: { stream: false },
  });

  const authority = "AUTHORITY_PUBKEY";
  const traderPdaIndex = 0;
  const childSubaccountIndex = 1;
  const symbol = "SOL";
  const usdc = (wholeUsdc: bigint) => wholeUsdc * 1_000_000n;

  const traderSnapshot = await client.api
    .traders()
    .getTraderStateSnapshot(authority, { traderPdaIndex });

  const childExists = traderSnapshot.snapshot.subaccounts.some(
    (subaccount) => subaccount.subaccountIndex === childSubaccountIndex
  );

  const instructions = [];

  if (!childExists) {
    instructions.push(
      await client.ixs.buildRegisterTrader({
        authority,
        marginType: MarginType.Isolated,
        traderPdaIndex,
        traderSubaccountIndex: childSubaccountIndex,
      })
    );
  }

  instructions.push(
    await client.ixs.buildSyncParentToChild({
      traderWallet: authority,
      traderPdaIndex,
      traderSubaccountIndex: childSubaccountIndex,
    })
  );

  instructions.push(
    await client.ixs.buildTransferCollateral({
      authority,
      traderPdaIndex,
      srcSubaccountIndex: 0,
      dstSubaccountIndex: childSubaccountIndex,
      amount: usdc(100n),
    })
  );

  const orderPacket = await client.ixs.orderPackets.buildLimitOrderPacket({
    symbol,
    side: Side.Bid,
    priceUsd: "150",
    baseUnits: "1",
    selfTradeBehavior: SelfTradeBehavior.CancelProvide,
    orderFlags: OrderFlags.None,
    cancelExisting: false,
  });

  instructions.push(
    await client.ixs.buildPlaceLimitOrder({
      authority,
      symbol,
      orderPacket,
      traderPdaIndex,
      traderSubaccountIndex: childSubaccountIndex,
    })
  );

  // Optional: mimics the server isolated-order routes' default cleanup step.
  // If this order closes the isolated position and leaves no resting orders,
  // the remaining child collateral is moved back to the cross account.
  instructions.push(
    await client.ixs.buildTransferCollateralChildToParent({
      authority,
      traderPdaIndex,
      childSubaccountIndex,
    })
  );

  // Send the instructions in a transaction signed by `authority`.
  // If you use `positionAuthority`, pass it to transfer/order builders and
  // sign the relevant instructions with that delegated authority.
  ```

  ```rust Rust theme={null}
  use phoenix_rise::{
      LimitOrderTicket, PhoenixHttpClient, PhoenixMetadata, PhoenixTxBuilder, Side, TraderKey,
  };
  use solana_keypair::read_keypair_file;
  use solana_signer::Signer;

  let keypair = read_keypair_file("PATH_TO_KEYPAIR.json")?;
  let parent_key = TraderKey::new(keypair.pubkey()); // pda_index 0, subaccount_index 0
  let child_key = TraderKey::new_with_idx(parent_key.authority(), 0, 1);

  let http = PhoenixHttpClient::new_from_env()?;
  let exchange = http.get_exchange().await?.into();
  let metadata = PhoenixMetadata::new(exchange);
  let builder = PhoenixTxBuilder::new(&metadata);

  let child_exists = http
      .traders()
      .get_trader_subaccount(
          &parent_key.authority(),
          child_key.pda_index,
          child_key.subaccount_index,
      )
      .await?
      .is_some();

  let mut instructions = Vec::new();

  if !child_exists {
      instructions.extend(builder.build_register_trader(
          child_key.authority(),
          child_key.pda_index,
          child_key.subaccount_index,
      )?);
  }

  instructions.extend(builder.build_sync_parent_to_child(
      child_key.authority(),
      parent_key.pda(),
      child_key.pda(),
  )?);

  instructions.extend(builder.build_transfer_collateral(
      child_key.authority(),
      parent_key.pda(),
      child_key.pda(),
      100.0,
  )?);

  let order_ticket = LimitOrderTicket::builder()
      .authority(child_key.authority())
      .trader_account(child_key.pda())
      .symbol("SOL")
      .side(Side::Bid)
      .price(150.0)
      .num_base_lots(1)
      .subaccount_index(child_key.subaccount_index)
      .build()?;

  instructions.extend(builder.place_limit_order(order_ticket).await?);

  // Optional: mimics the server isolated-order routes' default cleanup step.
  // If this order closes the isolated position and leaves no resting orders,
  // the remaining child collateral is moved back to the cross account.
  instructions.extend(builder.build_transfer_collateral_child_to_parent(
      child_key.authority(),
      child_key.pda(),
      parent_key.pda(),
  )?);

  // Send the instructions in a transaction signed by `authority`.
  ```
</CodeGroup>

The server routes also check that the parent has enough transferable collateral before creating the transfer instruction. If your app builds these instructions client-side, perform the same check from trader state so you do not try to move collateral reserved for existing margin requirements.

## Return isolated collateral to cross margin

Use `TransferCollateralChildToParent` when an isolated child account should return its collateral to the parent cross account. This instruction targets one child subaccount and transfers all available collateral back to subaccount `0`; it does not take an amount parameter.

The child account must have no active limit orders or positions. If the child account has no collateral left to transfer, the instruction no-ops on-chain.

This is useful after a close-position order. Place the close order first, then append `TransferCollateralChildToParent`; if the close leaves the isolated account with no active position and no active limit orders, the remaining collateral is moved back to the cross account.

<CodeGroup>
  ```ts TypeScript theme={null}
  const authority = "AUTHORITY_PUBKEY";
  const traderPdaIndex = 0;
  const isolatedSubaccountIndex = 1;

  const transferToParentIx =
    await client.ixs.buildTransferCollateralChildToParent({
      authority,
      traderPdaIndex,
      childSubaccountIndex: isolatedSubaccountIndex,
    });

  // Send the instruction in a transaction signed by `authority`.
  // The parent cross account and child isolated account must already exist.
  ```

  ```rust Rust theme={null}
  let parent_key = TraderKey::new(keypair.pubkey()); // pda_index 0, subaccount_index 0
  let child_key = TraderKey::new_with_idx(parent_key.authority(), 0, 1);

  let transfer_ixs = builder.build_transfer_collateral_child_to_parent(
      child_key.authority(),
      child_key.pda(),
      parent_key.pda(),
  )?;

  // Send the instructions in a transaction signed by `authority`.
  // The parent cross account and child isolated account must already exist.
  ```
</CodeGroup>
