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.