> ## 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.

# On-Chain Programs

> Integrate Solana programs with Phoenix perpetuals through the Rise Rust SDK and CPI helpers.

Use the Rise Rust CPI surface when your Solana program already has `AccountInfo`
handles and needs to invoke Phoenix, Ember, Flight, or Hawkeye directly. The SDK
does not fetch accounts or derive every PDA at CPI time. Your program validates
and resolves accounts, then adapts them into typed CPI contexts such as
`phoenix::PlaceMarketOrder`, `phoenix::PlaceStopLoss`, or
`hawkeye::ViewMargin`.

Reference implementations:

* [rise-public programs/example-program](https://github.com/Ellipsis-Labs/rise-public/tree/master/programs/example-program)
* [example-program/src/market.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/market.rs)
* [example-program/src/place\_stop\_loss.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/place_stop_loss.rs)
* [example-program/tests/common/mod.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/tests/common/mod.rs)

## Example Program Map

Use the example program as the source of truth for complete account lists. The
docs below show the shape of each CPI, but the linked files show the full outer
instruction, parameter struct, account metas, LiteSVM test setup, and log
assertions.

| Flow                                                                                                 | Program code                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | Test coverage                                                                                                                       |
| ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Market order, limit order, cancel by id, matching-engine return data, and Hawkeye margin read        | [market.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/market.rs), [place\_market\_order.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/place_market_order.rs), [place\_limit\_order.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/place_limit_order.rs), [cancel\_limit\_order.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/cancel_limit_order.rs) | [market\_orders.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/tests/market_orders.rs)       |
| Ember wrap then Phoenix deposit                                                                      | [deposit\_ember\_then\_phoenix.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/deposit_ember_then_phoenix.rs)                                                                                                                                                                                                                                                                                                                                                                            | [deposit\_withdraw.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/tests/deposit_withdraw.rs) |
| Phoenix withdraw then Ember unwrap                                                                   | [withdraw\_phoenix\_then\_ember.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/withdraw_phoenix_then_ember.rs)                                                                                                                                                                                                                                                                                                                                                                          | [deposit\_withdraw.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/tests/deposit_withdraw.rs) |
| Stop-loss placement and cancellation                                                                 | [place\_stop\_loss.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/place_stop_loss.rs), [cancel\_stop\_loss.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/cancel_stop_loss.rs)                                                                                                                                                                                                                                                               | [stop\_loss.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/tests/stop_loss.rs)               |
| Register isolated subaccount, sync parent capabilities, transfer collateral, trade, close, and sweep | [register\_subaccount\_sync\_transfer\_and\_market\_order.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/register_subaccount_sync_transfer_and_market_order.rs), [close\_subaccount\_position\_and\_sweep.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/close_subaccount_position_and_sweep.rs)                                                                                                                                             | [subaccounts.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/tests/subaccounts.rs)            |
| Shared fixture setup, dynamic accounts, account metas, compute budget, and log helpers               | [tests/common/mod.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/tests/common/mod.rs)                                                                                                                                                                                                                                                                                                                                                                                                       | [programs/example-program/tests](https://github.com/Ellipsis-Labs/rise-public/tree/master/programs/example-program/tests)           |

## Dependencies

Most on-chain programs should depend on the `phoenix-rise` facade with only the
`cpi` feature enabled. That profile exposes account byte decoders, instruction
layouts, and Pinocchio CPI helpers without the HTTP, WebSocket, RPC, or
transaction-builder graph.

```toml Cargo theme={null}
[dependencies]
phoenix-rise = { version = "0.3", default-features = false, features = ["cpi"] }
pinocchio = "0.9.4"

# Optional, but used by the public example program for instruction params,
# program ids, and program-id validation helpers.
borsh = { version = "1.6", features = ["derive"] }
pinocchio-pubkey = "0.3"
solana-pubkey = { version = "~3.0", default-features = false }
```

If you only want the instruction and CPI layer, depend on `phoenix-rise-ix`
directly.

```toml Cargo theme={null}
[dependencies]
phoenix-rise-ix = { version = "0.3", default-features = false, features = ["cpi"] }
pinocchio = "0.9.4"
```

The public example program uses the facade from the local workspace:

```toml Cargo theme={null}
phoenix-rise = { path = "../../rust/sdk", default-features = false, features = [
  "cpi",
] }
```

Source: [example-program/Cargo.toml](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/Cargo.toml).

## Imports

Using the facade crate:

```rust Rust theme={null}
use phoenix_rise::ix;
use phoenix_rise::ix::types::{OrderFlags, SelfTradeBehavior, Side};
use pinocchio::account_info::AccountInfo;
use pinocchio::program_error::ProgramError;
use pinocchio::{ProgramResult, msg};
```

Using the low-level instruction crate directly:

```rust Rust theme={null}
use phoenix_rise_ix::cpi::{CpiScratch, hawkeye, phoenix};
use phoenix_rise_ix::types::{OrderFlags, SelfTradeBehavior, Side};
use pinocchio::account_info::AccountInfo;
use pinocchio::program_error::ProgramError;
```

## CPI Shape

The typed CPI contexts are account-context structs. They borrow accounts from
your outer instruction and encode the expected Phoenix account order for you.
They do not own accounts and they do not resolve missing accounts from chain
state.

Phoenix order CPIs usually need:

* Phoenix program id and log authority.
* Global config and `PerpAssetMap`.
* Trader signer or delegated authority, plus the trader account.
* Market orderbook and spline collection.
* Dynamic `global_trader_index` accounts.
* Dynamic `active_trader_buffer` accounts.
* Optional accounts for stop losses, conditionals, Hawkeye, Ember, or Flight.

The example program keeps a fixed account prefix and passes the dynamic
`global_trader_index` and `active_trader_buffer` accounts at the tail. The tail
counts are carried in instruction data, so the loader can split the account
slice without allocation.

```rust Rust theme={null}
pub(crate) fn dynamic_tail<'a>(
    accounts: &'a [AccountInfo],
    fixed_account_count: usize,
    global_trader_index_count: usize,
    active_trader_buffer_count: usize,
) -> Result<(&'a [AccountInfo], &'a [AccountInfo]), ProgramError> {
    let gti_end = fixed_account_count
        .checked_add(global_trader_index_count)
        .ok_or(ProgramError::InvalidInstructionData)?;
    let expected = gti_end
        .checked_add(active_trader_buffer_count)
        .ok_or(ProgramError::InvalidInstructionData)?;

    require_exact_accounts(accounts, expected)?;
    Ok((
        &accounts[fixed_account_count..gti_end],
        &accounts[gti_end..expected],
    ))
}
```

Source: [example-program/src/common.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/common.rs).

## Market Context

A useful integration pattern is to create one outer context that knows how to
load and validate your instruction accounts, then add small helper methods that
project those accounts into the SDK's typed CPI contexts.

```rust Rust theme={null}
use phoenix_rise::ix;
use pinocchio::account_info::AccountInfo;
use pinocchio::program_error::ProgramError;
use pinocchio::{ProgramResult, msg};

use crate::common::dynamic_tail;
use crate::cpi::check_program_id;

pub(crate) struct MarketContext<'a> {
    phoenix_program: &'a AccountInfo,
    hawkeye_program: &'a AccountInfo,
    log_authority: &'a AccountInfo,
    global_config: &'a AccountInfo,
    trader: &'a AccountInfo,
    trader_account: &'a AccountInfo,
    perp_asset_map: &'a AccountInfo,
    orderbook: &'a AccountInfo,
    spline_collection: &'a AccountInfo,
    global_trader_index: &'a [AccountInfo],
    active_trader_buffer: &'a [AccountInfo],
}

impl<'a> MarketContext<'a> {
    const FIXED_ACCOUNT_COUNT: usize = 9;

    pub(crate) fn load(
        accounts: &'a [AccountInfo],
        global_trader_index_count: usize,
        active_trader_buffer_count: usize,
    ) -> Result<Self, ProgramError> {
        let (global_trader_index, active_trader_buffer) = dynamic_tail(
            accounts,
            Self::FIXED_ACCOUNT_COUNT,
            global_trader_index_count,
            active_trader_buffer_count,
        )?;

        let context = Self {
            phoenix_program: &accounts[0],
            hawkeye_program: &accounts[1],
            log_authority: &accounts[2],
            global_config: &accounts[3],
            trader: &accounts[4],
            trader_account: &accounts[5],
            perp_asset_map: &accounts[6],
            orderbook: &accounts[7],
            spline_collection: &accounts[8],
            global_trader_index,
            active_trader_buffer,
        };
        context.validate()?;
        Ok(context)
    }

    fn validate(&self) -> ProgramResult {
        check_program_id(self.phoenix_program, &ix::PHOENIX_PROGRAM_ID, "Phoenix")?;
        check_program_id(self.hawkeye_program, &ix::HAWKEYE_PROGRAM_ID, "Hawkeye")?;
        if !self.trader.is_signer() {
            msg!("trader must sign market action");
            return Err(ProgramError::MissingRequiredSignature);
        }
        Ok(())
    }

    fn place_market_order_accounts(&self) -> ix::cpi::phoenix::PlaceMarketOrder<'a> {
        ix::cpi::phoenix::PlaceMarketOrder {
            phoenix_program: self.phoenix_program,
            log_authority: self.log_authority,
            global_config: self.global_config,
            trader: self.trader,
            trader_account: self.trader_account,
            perp_asset_map: self.perp_asset_map,
            global_trader_index: self.global_trader_index,
            active_trader_buffer: self.active_trader_buffer,
            orderbook: self.orderbook,
            spline_collection: self.spline_collection,
        }
    }
}
```

Source: [example-program/src/market.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/market.rs).

## Invoke Phoenix

Once your outer context can build the typed CPI context, the invoke step is
small: create `CpiScratch`, pass instruction-specific args, and let the SDK
write the account metas and instruction data.

```rust Rust theme={null}
use crate::cpi::MAX_CPI_ACCOUNTS;
use crate::params::MarketOrderCpiParams;

impl<'a> MarketContext<'a> {
    pub(crate) fn invoke_market_order(
        &self,
        params: &MarketOrderCpiParams,
    ) -> ProgramResult {
        let market_order = self.place_market_order_accounts();

        let mut scratch = ix::cpi::CpiScratch::<
            { MAX_CPI_ACCOUNTS },
            { ix::cpi::phoenix::PlaceMarketOrder::MAX_DATA_LEN },
        >::new(self.phoenix_program);

        market_order.invoke(
            ix::cpi::phoenix::PlaceMarketOrderArgs {
                side: params.side,
                price_in_ticks: params.price_in_ticks,
                num_base_lots: params.num_base_lots,
                num_quote_lots: params.num_quote_lots,
                min_base_lots_to_fill: params.min_base_lots_to_fill,
                min_quote_lots_to_fill: params.min_quote_lots_to_fill,
                self_trade_behavior: params.self_trade_behavior,
                match_limit: params.match_limit,
                client_order_id: params.client_order_id,
                last_valid_slot: params.last_valid_slot,
                order_flags: params.order_flags,
                cancel_existing: params.cancel_existing,
            },
            &mut scratch,
        )
    }
}
```

`CpiScratch` owns fixed-size stack buffers for account infos, account metas, and
instruction data. For dynamic market accounts, size it to your integration's
upper bound and check `ctx.account_count()` before invoking if the account count
is user-controlled. If you already have reusable storage, use `CpiBuffers` and
`invoke_with_buffers(...)` instead.

## Stop Losses And Conditionals

Stop losses and conditional orders follow the same pattern, but the account
context includes the funder, position authority, conditional or stop-loss
account, and system program. Creating a stop-loss or conditional-order account
can incur rent if the account does not already exist.

```rust Rust theme={null}
let place_stop_loss = ix::cpi::phoenix::PlaceStopLoss {
    phoenix_program,
    log_authority,
    global_config,
    funder,
    trader_account,
    perp_asset_map,
    global_trader_index,
    active_trader_buffer,
    orderbook,
    spline_collection,
    position_authority,
    stop_loss_account,
    system_program,
};

let mut scratch = ix::cpi::CpiScratch::<
    { MAX_CPI_ACCOUNTS },
    { ix::cpi::phoenix::PlaceStopLoss::DATA_LEN },
>::new(phoenix_program);

place_stop_loss.invoke(
    ix::cpi::phoenix::PlaceStopLossArgs {
        trigger_price,
        execution_price,
        trade_side,
        execution_direction,
        order_kind,
    },
    &mut scratch,
)?;
```

Source: [example-program/src/place\_stop\_loss.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/place_stop_loss.rs).

## Collateral CPIs

Collateral flows often compose Ember and Phoenix CPIs in one outer instruction.
The example program wraps fake USDC into Phoenix collateral through Ember, then
deposits that collateral into the Phoenix trader account.

```rust Rust theme={null}
let ember_deposit = ix::cpi::ember::EmberDeposit {
    ember_program,
    trader,
    ember_state,
    usdc_mint,
    canonical_mint,
    trader_usdc_account,
    trader_phoenix_account,
    ember_vault,
    token_program,
};
let mut ember_scratch = ix::cpi::CpiScratch::<
    { ix::cpi::ember::EmberDeposit::ACCOUNT_COUNT },
    { ix::cpi::ember::EmberDeposit::DATA_LEN },
>::new(ember_program);
ember_deposit.invoke(
    ix::cpi::ember::EmberDepositArgs { amount: ember_amount },
    &mut ember_scratch,
)?;

let phoenix_deposit = ix::cpi::phoenix::PhoenixDeposit {
    phoenix_program,
    log_authority,
    global_config,
    trader,
    trader_token_account: trader_phoenix_account,
    trader_account,
    global_vault,
    token_program,
    global_trader_index,
    active_trader_buffer,
    permission_account: None,
};
let mut phoenix_scratch = ix::cpi::CpiScratch::<
    { MAX_CPI_ACCOUNTS },
    { ix::cpi::phoenix::PhoenixDeposit::DATA_LEN },
>::new(phoenix_program);
phoenix_deposit.invoke(
    ix::cpi::phoenix::PhoenixDepositArgs { amount: phoenix_amount },
    &mut phoenix_scratch,
)?;
```

Source: [example-program/src/deposit\_ember\_then\_phoenix.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/deposit_ember_then_phoenix.rs).

Withdrawals reverse the sequence: Phoenix withdraw first, then Ember unwrap.
Source: [example-program/src/withdraw\_phoenix\_then\_ember.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/withdraw_phoenix_then_ember.rs).

## Subaccount CPIs

Subaccount flows are a good example of composing several typed contexts. The
example registers the child trader account, syncs parent capabilities, transfers
collateral to the child, then reuses `MarketContext` to place the child order.

```rust Rust theme={null}
register.invoke(
    ix::cpi::phoenix::RegisterTraderArgs {
        max_positions,
        trader_pda_index,
        subaccount_index: child_subaccount_index,
    },
    &mut register_scratch,
)?;

sync.invoke(
    ix::cpi::phoenix::SyncParentToChildArgs,
    &mut sync_scratch,
)?;

transfer.invoke(
    ix::cpi::phoenix::TransferCollateralArgs { amount },
    &mut transfer_scratch,
)?;

let market_context = MarketContext::from_refs(MarketAccountRefs {
    phoenix_program,
    hawkeye_program,
    log_authority,
    global_config,
    trader: trader_authority,
    trader_account: child_trader_account,
    perp_asset_map,
    orderbook,
    spline_collection,
    global_trader_index,
    active_trader_buffer,
})?;
market_context.invoke_market_order_without_arenas(&order, "subaccount-open-order")?;
```

Source: [example-program/src/register\_subaccount\_sync\_transfer\_and\_market\_order.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/register_subaccount_sync_transfer_and_market_order.rs).

## Return Data

Phoenix order CPIs can return matching-engine data. Decode return data instead
of parsing logs.

```rust Rust theme={null}
use phoenix_rise::ix::return_data::decode_matching_engine_cpi_response;
use pinocchio::cpi::get_return_data;

let Some(return_data) = get_return_data() else {
    return Ok(());
};

let response = decode_matching_engine_cpi_response(return_data.as_slice())?;
if let Some(order_id) = response.order_id() {
    msg!(&format!(
        "order_id_price_in_ticks={} order_sequence_number={}",
        order_id.price_in_ticks,
        order_id.order_sequence_number,
    ));
}
```

Source: [example-program/src/common.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/common.rs).

## Hawkeye Views

Hawkeye is the read-only program for authoritative margin, liquidation-price,
BBO, and funding views. Invoke it when local SDK math is not enough and you need
the result the on-chain programs would use. Hawkeye writes versioned return data;
decode those bytes instead of parsing logs.

Hawkeye program id: `RiSeVw3ZjNfsaXPRb4mgaqYaEEt41pNNJoDvVh7pgQj`.
Use it to validate the CPI program account on-chain and to assert that
simulation return data came from Hawkeye off-chain.

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

  const result = await client.rpc.hawkeye.viewMargin({
    authority: "AUTHORITY_PUBKEY",
    traderPdaIndex: 0,
    traderSubaccountIndex: 0,
  });

  if (result.returnData?.programId !== HAWKEYE_PROGRAM_ADDRESS) {
    throw new Error("unexpected Hawkeye return-data program");
  }
  ```

  ```rust Rust theme={null}
  use phoenix_rise::ix;
  use pinocchio::account_info::AccountInfo;
  use pinocchio::ProgramResult;

  use crate::cpi::check_program_id;

  fn validate_hawkeye_program(hawkeye_program: &AccountInfo) -> ProgramResult {
      check_program_id(hawkeye_program, &ix::HAWKEYE_PROGRAM_ID, "Hawkeye")
  }
  ```
</CodeGroup>

Hawkeye instructions are normal Solana instructions, so they can be invoked
off-chain in a transaction simulation or on-chain through CPI. The current typed
Pinocchio CPI helper in the Rust SDK is `ix::cpi::hawkeye::ViewMargin`; the
other views are exposed through off-chain instruction builders and can be
mirrored on-chain with the same account metas and discriminators if your program
needs them.

| View              | Hawkeye ix               | Return data                                                    | SDK entry points                                                                                                                                                                                                    |
| ----------------- | ------------------------ | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Account margin    | `view_margin`            | `ViewMarginReturn` / `HawkeyeMarginReturn`                     | On-chain `ix::cpi::hawkeye::ViewMargin`; TypeScript `client.rpc.hawkeye.viewMargin(...)` or `buildHawkeyeViewMarginIx(...)`; Rust `PhoenixHawkeyeClient::view_margin(...)` or `create_hawkeye_view_margin_ix(...)`  |
| Asset margin      | `view_margin_for_asset`  | `ViewAssetReturn` / `HawkeyeAssetReturn`                       | TypeScript `client.rpc.hawkeye.viewMarginForAsset(...)` or `buildHawkeyeViewMarginForAssetIx(...)`; Rust `PhoenixHawkeyeClient::view_margin_for_asset(...)` or `create_hawkeye_view_margin_for_asset_ix(...)`       |
| Liquidation price | `view_liquidation_price` | `ViewLiquidationPriceReturn` / `HawkeyeLiquidationPriceReturn` | TypeScript `client.rpc.hawkeye.viewLiquidationPrice(...)` or `buildHawkeyeViewLiquidationPriceIx(...)`; Rust `PhoenixHawkeyeClient::view_liquidation_price(...)` or `create_hawkeye_view_liquidation_price_ix(...)` |
| Best bid/offer    | `view_bbo`               | `ViewBboReturn` / `HawkeyeBboReturn`                           | TypeScript `client.rpc.hawkeye.viewBbo(...)` or `buildHawkeyeViewBboIx(...)`; Rust `PhoenixHawkeyeClient::view_bbo(...)` or `create_hawkeye_view_bbo_ix(...)`                                                       |
| Funding           | `view_funding`           | `ViewFundingReturn` / `HawkeyeFundingReturn`                   | TypeScript `client.rpc.hawkeye.viewFunding(...)` or `buildHawkeyeViewFundingIx(...)`; Rust `PhoenixHawkeyeClient::view_funding(...)` or `create_hawkeye_view_funding_ix(...)`                                       |

Sources: [rust/ix/src/hawkeye.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/rust/ix/src/hawkeye.rs),
[ts/src/hawkeye.ts](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/hawkeye.ts),
[rust/core/src/hawkeye\_client.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/rust/core/src/hawkeye_client.rs),
and [ts/src/rpc.ts](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/src/rpc.ts).

### On-chain CPI

On-chain programs read Hawkeye return data the same way they read Phoenix return
data: invoke the program, call `get_return_data()` immediately, assert the
return-data program id is Hawkeye, then decode the expected struct. Return data
is overwritten by the next CPI that sets it, so decode or copy it before another
Phoenix or Hawkeye call.

The example program invokes `hawkeye::ViewMargin` after market orders and
decodes the return into `ViewMarginReturn`.

```rust Rust theme={null}
let view_margin = ix::cpi::hawkeye::ViewMargin {
    hawkeye_program,
    phoenix_program,
    global_config,
    global_trader_index,
    active_trader_buffer,
    perp_asset_map,
    trader: trader_account,
};

let mut scratch = ix::cpi::CpiScratch::<
    { MAX_CPI_ACCOUNTS },
    { ix::cpi::hawkeye::ViewMargin::DATA_LEN },
>::new(hawkeye_program);

let margin = view_margin.invoke_and_decode(&mut scratch)?;
msg!(&format!(
    "collateral={} free={} maintenance={} liquidatable={}",
    margin.collateral_quote_lots,
    margin.free_collateral_quote_lots,
    margin.maintenance_margin_quote_lots,
    margin.is_liquidatable,
));
```

If you need the manual decode path, use the same guard the helper uses
internally.

```rust Rust theme={null}
use phoenix_rise::ix::hawkeye::{ViewMarginReturn, decode_hawkeye_return};
use pinocchio::cpi::get_return_data;

let return_data = get_return_data().ok_or(ProgramError::InvalidAccountData)?;
if return_data.program_id() != &ix::HAWKEYE_PROGRAM_ID.to_bytes() {
    return Err(ProgramError::InvalidAccountData);
}

let margin = decode_hawkeye_return::<ViewMarginReturn>(
    return_data.as_slice(),
    "view_margin",
)
.map_err(|_| ProgramError::InvalidAccountData)?;
```

Source: [example-program/src/market.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/programs/example-program/src/market.rs)
and [rust/ix/src/cpi.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/rust/ix/src/cpi.rs).

### Off-chain simulation

For applications and services, prefer the SDK Hawkeye RPC helpers. They build a
read-only simulation transaction, validate that return data came from the
Hawkeye program id, and decode the bytes into the typed return shape.

<CodeGroup>
  ```ts TypeScript theme={null}
  const margin = await client.rpc.hawkeye.viewMargin({
    authority: "AUTHORITY_PUBKEY",
    traderPdaIndex: 0,
    traderSubaccountIndex: 0,
  });

  const liquidation = await client.rpc.hawkeye.viewLiquidationPrice({
    authority: "AUTHORITY_PUBKEY",
    traderPdaIndex: 0,
    traderSubaccountIndex: 0,
    symbol: "SOL",
  });

  const bbo = await client.rpc.hawkeye.viewBbo({ symbol: "SOL" });

  console.log(margin.returnData?.decoded.freeCollateralQuoteLots);
  console.log(liquidation.returnData?.decoded.liquidationPriceTicks);
  console.log(bbo.returnData?.decoded.markPriceTicks);
  ```

  ```rust Rust theme={null}
  use phoenix_rise::{PhoenixHawkeyeClient, TraderKey};

  let trader_key = TraderKey::new(authority);
  let hawkeye = PhoenixHawkeyeClient::new(rpc_url, &metadata);

  let margin = hawkeye
      .view_margin_for_trader(trader_key.pda())
      .await?;
  let liquidation = hawkeye
      .view_liquidation_price_for_trader(trader_key.pda(), sol_asset_id)
      .await?;
  let bbo = hawkeye.view_bbo("SOL").await?;

  println!("free collateral={}", margin.value.free_collateral_quote_lots);
  println!(
      "liquidation price ticks={}",
      liquidation.value.liquidation_price_ticks
  );
  println!("mark price ticks={}", bbo.value.mark_price_ticks);
  ```
</CodeGroup>

If you already have accounts resolved, you can build a Hawkeye instruction
directly and still let the SDK decode the return data.

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

  const ix = buildHawkeyeViewLiquidationPriceIx({
    phoenixProgramAddress,
    globalConfigurationAddress,
    globalTraderIndex,
    activeTraderBuffer,
    perpAssetMap,
    traderAccount,
    assetId: 0,
  });

  const simulation = await client.rpc.hawkeye.simulateInstruction(ix);
  const decoded = simulation.returnData?.decoded ?? null;

  if (decoded?.kind === "view_liquidation_price") {
    console.log(decoded.liquidationPriceTicks);
  }
  ```

  ```rust Rust theme={null}
  use phoenix_rise::ix::hawkeye::{
      HawkeyeReturnData,
      HawkeyeTraderViewAccounts,
      create_hawkeye_view_liquidation_price_ix,
  };

  let accounts = HawkeyeTraderViewAccounts {
      phoenix_program_id,
      global_config,
      global_trader_index,
      active_trader_buffer,
      perp_asset_map,
      trader: trader_account,
  };
  let ix = create_hawkeye_view_liquidation_price_ix(accounts, sol_asset_id);
  let simulation = hawkeye.simulate_instruction(ix.into()).await?;

  if let HawkeyeReturnData::LiquidationPrice(value) = simulation.value {
      println!("{}", value.liquidation_price_ticks);
  }
  ```
</CodeGroup>

Reference coverage: [ts/tests/sdk-localnet-flows.test.ts](https://github.com/Ellipsis-Labs/rise-public/blob/master/ts/tests/sdk-localnet-flows.test.ts)
executes all five Hawkeye views in LiteSVM, and
[rust/sdk/tests/sdk\_localnet\_fixture\_tests.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/rust/sdk/tests/sdk_localnet_fixture_tests.rs)
asserts Hawkeye return data is emitted by the expected program id.

## Supported CPI Contexts

The typed CPI surface includes:

* Trader lifecycle: `RegisterTrader`, `SetTraderCapabilitiesDelegated`, `UpdateTraderState`, `SyncParentToChild`.
* Order flow: `PlaceMarketOrder`, `PlaceLimitOrder`, `PlaceMarketOrderDelegated`, `PlaceMultiLimitOrder`, `CancelAll`, `CancelUpTo`, `CancelOrdersById`, `CancelAllPlusConditional`.
* Collateral and subaccounts: `PhoenixDeposit`, `PhoenixWithdraw`, `TransferCollateral`, `TransferCollateralChildToParent`.
* Stop losses and conditionals: `CreateConditionalOrdersAccount`, `PlaceStopLoss`, `CancelStopLoss`, `PlacePositionConditionalOrder`, `PlaceAttachedConditionalOrder`, `PlaceLimitOrderWithConditionals`, `CancelConditionalOrder`.
* Ember collateral movement: `ember::EmberDeposit`, `ember::EmberWithdraw`.
* Hawkeye reads: `hawkeye::ViewMargin` / `hawkeye::HawkeyeViewMargin`.

For full account lists and data lengths, inspect
[rust/ix/src/cpi.rs](https://github.com/Ellipsis-Labs/rise-public/blob/master/rust/ix/src/cpi.rs).

## Testing

Use [LiteSVM Testing](/sdk/litesvm-testing) for local integration tests. The
example program tests cover deposits, withdrawals, market orders, limit orders,
cancels, stop losses, Hawkeye reads, and isolated subaccount collateral flows.
