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 = 0is the cross-margin account. It is created during onboarding withmax_positionsbetween32and128inclusive; the default is128.subaccount_index > 0is an isolated account. Isolated accounts havemax_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 andpda_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 |
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.
Set position authority
Use the on-chainDelegateTrader 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.
The SDK parameter traderPdaIndex corresponds to the account’s portfolio_index, and traderSubaccountIndex corresponds to subaccount_index.
TypeScript
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 separateposition_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(...) |
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.
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.
Create isolated accounts
Onboarding creates the user’s cross-margin trader account attraderPdaIndex = 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.
Use
traderSubaccountIndex = 0 only for the cross account. Isolated accounts use non-zero subaccount indexes.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:- Pick a non-zero isolated subaccount index.
- If that child account does not exist, register it.
- Sync the parent cross account to the child account.
- Transfer collateral from the parent cross account to the child account with
TransferCollateral. - Place the order on the child account.
- Optionally append
TransferCollateralChildToParentto 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.
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.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.Return isolated collateral to cross margin
UseTransferCollateralChildToParent 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.