Skip to main content
This guide walks through opening a 2× long on SOL-PERP from a clean wallet state, using the Codama-generated TS client.

Prerequisites

  • Wallet funded on devnet (solana airdrop 2)
  • USDC (devnet) in the wallet, mint is on the program constants page
  • Perp market initialized (it is, on devnet, at market_index = 0)

1. Create a user account

import { getInitializeUserAccountInstruction, findUserAccountPda } from
  '@/app/generated/perp/instructions';

const [userAccount] = await findUserAccountPda({ authority: wallet.address });

const ix = getInitializeUserAccountInstruction({
  authority: wallet,
  payer: wallet,
  userAccount,
});

await sendTransaction(ix);

2. Deposit USDC collateral

import { getDepositCollateralInstruction } from '@/app/generated/perp/instructions';

const ix = getDepositCollateralInstruction({
  signer: wallet,
  userAccount,
  market: marketPda,
  userTokenAccount: userUsdcAta,
  collateralVault: market.collateralVault,
  amount: 1_000_000_000n,   // 1,000 USDC (6 decimals)
});

3. Post the oracle update

import { PythSolanaReceiver } from '@pythnetwork/pyth-solana-receiver';
const hermes = await fetch(
  `https://hermes.pyth.network/v2/updates/price/latest?ids[]=${SOL_USD_FEED_ID}`
).then((r) => r.json());

const postIxs = await receiver.buildPostPriceUpdateInstructions(
  hermes.binary.data[0],
);

4. Place a market long

import { getPlaceOrderInstruction } from '@/app/generated/perp/instructions';

const ix = getPlaceOrderInstruction({
  signer: wallet,
  userAccount,
  market: marketPda,
  oracle: priceUpdateAccount,   // from step 3
  orderbook: orderbookPda,
  collateralVault: market.collateralVault,
  feeVault: market.feeVault,
  // remainingAccounts: counterparty user accounts for up to 4 fills
  params: {
    price: 0n,                       // ignored for market orders
    baseAmount: 2_000_000_000n,      // 2 SOL (9 decimals) → ≈2× leverage on 1k USDC
    direction: { __kind: 'Long' },
    postOnly: false,
    isMarket: true,
    maxSlippageBps: 100,
  },
});

await sendTransaction([...postIxs, ix]);

5. Close the position

A closing order is a market order in the opposite direction:
const closeIx = getPlaceOrderInstruction({
  ...commonAccounts,
  params: {
    ...,
    baseAmount: 2_000_000_000n,
    direction: { __kind: 'Short' },  // closes a long
    isMarket: true,
    maxSlippageBps: 100,
  },
});
Realized PnL flows into user_account.collateral automatically. Withdraw with getWithdrawCollateralInstruction (authority only, delegates cannot withdraw).

Things to watch

If the oracle post instruction is not prepended, place_order will revert with InvalidOraclePrice or StaleOracle. The price must land on-chain in the same transaction as the trade.
The matching loop processes up to 4 fills per tx. For larger fills, issue multiple place_order transactions, the remaining size will walk the book on each call.