Skip to content
 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rise SDK

Rise is the developer-facing SDK surface for Phoenix perpetuals. It currently ships as:

  • rise/ts: the TypeScript SDK, with HTTP route clients, a unified exchange-aware client, instruction builders, Flight helpers, and WebSocket adapters
  • rise/rust: the Rust workspace, centered on the phoenix-rise crate, with typed HTTP/WS clients, transaction builders, math helpers, and low-level instruction builders

If you are deciding where to start:

  • Reach for PhoenixHttpClient / client.api when you need public HTTP data or invite activation
  • Reach for createPhoenixClient(...) when you want exchange metadata, PDA derivation, order-packet helpers, and client.ixs
  • Reach for client.streams or createPhoenixWsClient(...) when you want typed live adapters
  • Reach for PhoenixTxBuilder in Rust when you want to construct and sign your own Solana instructions

Runnable Entry Points

TypeScript:

Rust:

Onboarding: Access Code vs Referral Code

These invite routes are not interchangeable:

  • Use POST /v1/invite/activate when you have an access code / allowlist code. Send that value as code.
  • Use POST /v1/invite/activate-with-referral when you have a referral code. Send that value as referral_code.

TypeScript:

import { PhoenixHttpClient } from "@ellipsis-labs/rise";

const client = new PhoenixHttpClient({
  apiUrl: "https://perp-api.phoenix.trade",
});

const authority = "AUTHORITY_PUBKEY";

const activatedWithAccessCode = await client.invite().activateInvite({
  authority,
  code: "ACCESS_CODE",
});

const activatedWithReferral = await client.invite().activateInviteWithReferral({
  authority,
  referral_code: "REF_CODE",
});

Rust:

use phoenix_rise::PhoenixHttpClient;
use solana_pubkey::Pubkey;
use std::str::FromStr;

let client = PhoenixHttpClient::new_from_env()?;
let authority = Pubkey::from_str("AUTHORITY_PUBKEY")?;

let trader_from_access = client
    .invite()
    .activate_invite(&authority, "ACCESS_CODE")
    .await?;

let trader_from_referral = client
    .invite()
    .activate_referral(&authority, "REF_CODE")
    .await?;

Use register_trader.rs when you want a ready-to-run Rust version of this flow.

Fetching Exchange, Market, and Trader State

The HTTP surface is intentionally split by what kind of state you want:

  • exchange().getSnapshot(): exchange-wide state plus every market's current config snapshot
  • exchange().getMarket(symbol): one market's fees, risk, funding cadence, and configuration
  • orderbook().getOrderbook(symbol): an HTTP L2 snapshot for one market
  • traders().getTraderStateSnapshot(...) in TypeScript or traders().get_trader(...) in Rust: a trader-centric view of collateral, positions, orders, and triggers
  • markets().getMarketStatsHistory(...) and funding().getFundingRateHistory(...): time-series data for frontends, vault products, and analytics

TypeScript:

import { PhoenixHttpClient } from "@ellipsis-labs/rise";

const client = new PhoenixHttpClient({
  apiUrl: "https://perp-api.phoenix.trade",
});

const symbol = "SOL";
const authority = "AUTHORITY_PUBKEY";

const [snapshot, market, orderbook, trader] = await Promise.all([
  client.exchange().getSnapshot(),
  client.exchange().getMarket(symbol),
  client.orderbook().getOrderbook(symbol),
  client.traders().getTraderStateSnapshot(authority, { traderPdaIndex: 0 }),
]);

Rust:

use phoenix_rise::PhoenixHttpClient;
use solana_pubkey::Pubkey;
use std::str::FromStr;

let client = PhoenixHttpClient::new_from_env()?;
let authority = Pubkey::from_str("AUTHORITY_PUBKEY")?;

let snapshot = client.exchange().get_snapshot().await?;
let market = client.markets().get_market("SOL").await?;
let trader_subaccounts = client.traders().get_trader(&authority).await?;

For broader TypeScript walkthroughs, see 01-http-client.ts and phoenix-client-example.ts.

Order Placement and Cancellation

The SDK intentionally separates packet construction from instruction construction:

  • build packet sizes and prices with client.orderPackets
  • build or wrap the actual Solana instructions with client.ixs
  • use the lower-level builders when you need conditional-account setup or other specialized flows

TypeScript:

import {
  Direction,
  Side,
  StopLossOrderKind,
  createPhoenixClient,
} from "@ellipsis-labs/rise";

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

const authority = "AUTHORITY_PUBKEY";
const symbol = "SOL-PERP";

const limitPacket = await client.orderPackets.buildLimitOrderPacket({
  symbol,
  side: Side.Bid,
  priceUsd: "150.50",
  baseUnits: "0.25",
});

const placeLimitIx = await client.ixs.placeLimitOrder({
  authority,
  symbol,
  orderPacket: limitPacket,
});

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

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

const stopLossIx = await client.ixs.buildPlaceStopLoss({
  authority,
  symbol,
  tradeSide: Side.Ask,
  executionDirection: Direction.LessThan,
  orderKind: StopLossOrderKind.IOC,
  triggerPrice: 1420n,
});

const cancelByIdIx = await client.ixs.buildCancelOrdersById({
  authority,
  symbol,
  orders: [{ price: 1500n, orderSequenceNumber: "123" }],
});

const cancelAllIx = await client.ixs.buildCancelAll({ authority, symbol });

const cancelStopLossIx = await client.ixs.buildCancelStopLoss({
  authority,
  symbol,
  executionDirection: Direction.LessThan,
});

buildPlaceStopLoss(...) takes tick-based trigger prices. When you are starting from USD prices, convert them from market metadata first, or reuse the conditional-order patterns in 05-cancel-all-conditional-orders.ts.

Rust:

use phoenix_rise::{
    BracketLeg, BracketLegOrders, BracketLegTicket, CancelId, LimitOrderTicket,
    MarketOrderTicket, PhoenixTxBuilder, Side, TraderKey,
};
use solana_rpc_client::nonblocking::rpc_client::RpcClient;
use std::sync::Arc;

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

let limit_ixs = builder
    .place_limit_order(
        LimitOrderTicket::builder()
            .authority(trader.authority())
            .trader_account(trader.pda())
            .symbol("SOL-PERP")
            .side(Side::Bid)
            .price(150.50)
            .num_base_lots(25_000)
            .build()?,
    )
    .await?;

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

let bracket_ixs = builder
    .place_position_bracket_order(
        trader.authority(),
        trader.pda(),
        "SOL-PERP",
        Side::Bid,
        BracketLegTicket::new(
            Arc::new(RpcClient::new("https://api.mainnet-beta.solana.com".into())),
            BracketLegOrders {
                stop_loss: Some(BracketLeg::new(142.0)),
                take_profit: None,
            },
        ),
    )
    .await?;

let cancel_ixs = builder.build_cancel_orders(
    trader.authority(),
    trader.pda(),
    "SOL-PERP",
    vec![CancelId::new(1500, 123)],
)?;

Runnable examples:

Flight Builder Activation and Routed Orders

Flight is the builder-routing layer. The important pieces are:

  • the builder still needs a Phoenix trader account
  • builder registration is its own on-chain instruction
  • the builder's associated trader account is the fee collector for Flight-routed orders
  • once a client is configured with flight: { builderAuthority, ... }, supported order instructions are wrapped automatically

When you register Flight against a builder authority and its associated trader account, all builder fees from Flight-routed orders accrue to that builder trader account. Those fees are withdrawable from the Phoenix frontend.

Use embedded wallets for Flight integrations. Integrators are strongly encouraged to provision an embedded wallet per user rather than routing Flight orders through the user's raw/external wallet. A raw wallet shares its Phoenix trader state with every other dapp the user trades on, which results in bad UX (positions, balances, and orders bleeding across integrations). Embedded wallets isolate per-integration state.

TypeScript:

import {
  MarginType,
  Side,
  createPhoenixClient,
  flight,
} from "@ellipsis-labs/rise";

const builderAuthority = "BUILDER_AUTHORITY";

const client = createPhoenixClient({
  apiUrl: "https://perp-api.phoenix.trade",
  rpcUrl: "https://api.mainnet-beta.solana.com",
  ws: false,
  flight: {
    builderAuthority,
    builderPdaIndex: 0,
    builderSubaccountIndex: 0,
  },
});

const registerTraderIx = await client.ixs.buildRegisterTrader({
  authority: builderAuthority,
  marginType: MarginType.Cross,
});

const registerBuilderIx = await flight.buildRegisterBuilderIx({
  traderAuthority: builderAuthority,
  traderPdaIndex: 0,
  traderSubaccountIndex: 0,
  feeBps: 25n,
});

const routedMarketIx = await client.ixs.placeMarketOrder({
  authority: "TRADER_AUTHORITY",
  symbol: "SOL-PERP",
  orderPacket: await client.orderPackets.buildMarketOrderPacket({
    symbol: "SOL-PERP",
    side: Side.Bid,
    baseUnits: "0.25",
  }),
});

console.log(routedMarketIx.programAddress === flight.FLIGHT_PROGRAM_ADDRESS);

Rust:

use phoenix_rise::ix::flight::{RegisterBuilderParams, create_register_builder_ix};
use phoenix_rise::{MarketOrderTicket, PhoenixFlightClient, PhoenixTxBuilder, Side};

let register_builder_ix = create_register_builder_ix(
    RegisterBuilderParams::builder()
        .trader_authority(builder_authority)
        .trader_account(builder_trader_account)
        .fee_bps(25)
        .build()?,
)?;

let flight = PhoenixFlightClient::new(builder_authority, 0, 0);

let routed_ixs = builder
    .place_market_order(
        MarketOrderTicket::builder()
            .authority(trader_authority)
            .trader_account(trader_account)
            .symbol("SOL-PERP")
            .side(Side::Bid)
            .num_base_lots(25_000)
            .build()?,
    )
    .await?
    .into_iter()
    .map(|ix| flight.try_wrap_order_instruction(ix, trader_authority))
    .collect::<Result<Vec<_>, _>>()?;

Runnable Flight examples:

For more Flight-specific TypeScript examples, see rise/ts/src/flight/README.md.

Live Market Data

Use the typed WebSocket adapters when you want continuous updates instead of a single HTTP snapshot.

TypeScript:

import { createPhoenixClient } from "@ellipsis-labs/rise";

const client = createPhoenixClient({
  apiUrl: "https://perp-api.phoenix.trade",
  ws: { connectMode: "eager" },
});

for await (const update of client.streams!.l2Book("SOL-PERP")) {
  console.log(update.bids[0], update.asks[0]);
}

Ready-to-run TypeScript stream examples:

About

Rise is the developer-facing SDK for Phoenix perpetuals

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages