Skip to main content
Do not build new integrations around /v1/invite/activate or /v1/referral/activate. These sponsored activation routes will be deprecated on June 30, 2026.
Most users should onboard in the Phoenix app at phoenix.trade. Use the SDK flows below when you are building onboarding into your own app. There are two SDK onboarding paths:
  1. With a referral code: call POST /v1/referral/activate-tx. This path requires the user-controlled trader authority to sign.
  2. Without a referral code: call POST /v1/exchange/build-register-ixs, build and sign the transaction locally, then submit it to POST /v1/exchange/send-register-ixs. This path does not validate the trader authority signature and can register off-curve authorities such as PDAs.
Signer handling differs by path. For referral-code activation, the user authority must sign the transaction request, and the API adds the Phoenix referral onboarder signature before submitting it. For the no-referral builder flow, traderAuthority is only the pubkey used to derive the trader account; it is not required to be a signer. The transaction sent to send-register-ixs must be signed by the fee payer and any other signer accounts you add. In the no-referral builder flow, pda_index and subaccount_index must be 0; max_positions is optional and defaults to 128.

With a referral code

Use /v1/referral/activate-tx when the user has a valid referral code. The TypeScript SDK builds the transaction, checks whether the default trader account needs to be registered, and submits the signed transaction request to the API. If your app pays for onboarding, pass feePayer and sign the transaction with both the payer and the trader authority. If the trader pays, omit feePayer.
import { createPhoenixClient, type Authority } from "@ellipsis-labs/rise";
import { createKeyPairSignerFromBytes } from "@solana/signers";
import {
  createSolanaRpc,
  partiallySignTransaction,
} from "@solana/kit";

declare const traderKeypairBytes: Uint8Array;

const client = createPhoenixClient({
  apiUrl: "https://perp-api.phoenix.trade",
  rpcUrl: "https://api.mainnet-beta.solana.com",
  exchangeMetadata: { stream: false },
});
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const traderSigner = await createKeyPairSignerFromBytes(traderKeypairBytes);
const traderAuthority = traderSigner.address as Authority;
const latestBlockhash = await rpc
  .getLatestBlockhash({ commitment: "finalized" })
  .send();

const built = await client.api.invite().buildActivateReferralTxRequest({
  referralCode: "REFERRAL_CODE",
  traderAuthority,
  traderPdaIndex: 0,
  traderSubaccountIndex: 0,
  recentBlockhash: latestBlockhash.value.blockhash,
  lastValidBlockHeight: latestBlockhash.value.lastValidBlockHeight,
  registerTraderMaxPositions: 128n,
  rpc,
  signTransaction: (transaction) =>
    partiallySignTransaction([traderSigner.keyPair], transaction),
});

const response = await client.api.invite().activateReferralTx(built.request);
console.log(response.signature, response.trader_pda, response.status);
Full runnable examples:

Without a referral code

Use this path when a builder wants to register and onboard a trader without a referral code. The flow is:
  1. Call POST /v1/exchange/build-register-ixs with the trader authority pubkey and the wallet that will pay transaction fees and account rent.
  2. Build a Solana transaction locally with those returned instructions, your current blockhash, and your chosen fee payer.
  3. Sign the transaction with the fee payer and any other signer accounts you add.
  4. Send the base64-encoded wire transaction to POST /v1/exchange/send-register-ixs.
  5. The API validates the transaction, signs with the Phoenix onboarder, simulates it, verifies the onboarder pays no lamports, sends it to Phoenix’s RPC, and returns the transaction signature.
The no-referral path does not validate that traderAuthority can sign. You may pass an on-curve wallet address or an off-curve PDA as the trader authority. The fee payer must not be the Phoenix onboarder key. Your app should await confirmation for the returned signature.
import {
  createPhoenixClient,
  type Authority,
  type RegisterIxInstruction,
} from "@ellipsis-labs/rise";
import { createKeyPairSignerFromBytes } from "@solana/signers";
import {
  AccountRole,
  address,
  appendTransactionMessageInstructions,
  compileTransaction,
  createSolanaRpc,
  createTransactionMessage,
  getBase64EncodedWireTransaction,
  partiallySignTransaction,
  pipe,
  setTransactionMessageFeePayer,
  setTransactionMessageLifetimeUsingBlockhash,
  type Blockhash,
} from "@solana/kit";

declare const traderAuthority: Authority;
declare const feePayerKeypairBytes: Uint8Array;

const toInstruction = (ix: RegisterIxInstruction) => ({
  programAddress: address(ix.programId),
  accounts: ix.keys.map((account) => ({
    address: address(account.pubkey),
    role: account.isSigner
      ? account.isWritable
        ? AccountRole.WRITABLE_SIGNER
        : AccountRole.READONLY_SIGNER
      : account.isWritable
        ? AccountRole.WRITABLE
        : AccountRole.READONLY,
  })),
  data: Uint8Array.from(ix.data),
});

const client = createPhoenixClient({
  apiUrl: "https://perp-api.phoenix.trade",
  rpcUrl: "https://api.mainnet-beta.solana.com",
  exchangeMetadata: { stream: false },
});
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const feePayerSigner = await createKeyPairSignerFromBytes(feePayerKeypairBytes);
const txFeePayer = feePayerSigner.address as Authority;
const latestBlockhash = await rpc
  .getLatestBlockhash({ commitment: "finalized" })
  .send();

const built = await client.api.exchange().buildRegisterIxs({
  traderAuthority,
  txFeePayer,
  maxPositions: 128,
});

const message = pipe(
  createTransactionMessage({ version: 0 }),
  (tx) => setTransactionMessageFeePayer(txFeePayer, tx),
  (tx) =>
    setTransactionMessageLifetimeUsingBlockhash(
      {
        blockhash: latestBlockhash.value.blockhash as Blockhash,
        lastValidBlockHeight: latestBlockhash.value.lastValidBlockHeight,
      },
      tx,
    ),
  (tx) => appendTransactionMessageInstructions(
    built.instructions.map(toInstruction),
    tx,
  ),
);

const signed = await partiallySignTransaction(
  [feePayerSigner.keyPair],
  compileTransaction(message),
);

const response = await client.api.exchange().sendRegisterIxs({
  transaction: getBase64EncodedWireTransaction(signed),
  traderAuthority,
  txFeePayer,
  maxPositions: 128,
  traderPdaIndex: 0,
  traderSubaccountIndex: 0,
});

console.log(response.signature, response.traderPda);
Full runnable examples:

After onboarding

Both paths activate the trader’s default cross-margin account at traderPdaIndex = 0 and traderSubaccountIndex = 0. For account indexes and isolated subaccounts, see Trader Accounts.