Program Instructions Reference
Complete reference for all 30 instructions exposed by the SportsPerp program. Every instruction except the three insurance-admin entries (deposit_insurance, withdraw_insurance, configure_insurance) ships with a typed builder in @sportsperp/sdk; admin keepers invoke the insurance trio directly via the IDL. Instruction indices match the on-chain Anchor discriminator ordering.
Canonical source: programs/obv-perps/src/lib.rs.
Trading (user-invoked)
open_position
Opens a new long or short position.
client.openPosition({
marketId: number,
direction: Direction, // Long (0) | Short (1)
collateralAmount: BN, // 10^6 scale
leverage: number, // 10^2 scale (500 = 5x, 200 = 2x)
})Required accounts: market, position (init), liquidity_pool, pool_token_account, user_token_account, user (signer), USDC mint, token program, system program, rent.
Errors: MarketPaused, OracleStale, InvalidLeverage (exceeds market max), MarginTierViolation, BelowMinPositionSize, PositionExceedsCapacityCap, InsufficientCollateral, RiskFloorBreached (pool_token.amount < pool.risk_floor).
close_position
Closes an existing position at current mark EMA. Settles PnL, funding, and 10-bps taker fee.
client.closePosition({ marketId: number })Accounts: market, position (close), liquidity_pool, pool_token_account, user_token_account, user (signer), token program.
Errors: NoPosition, OracleStale, MarketPaused, PoolInsufficient.
partial_close_position
Closes a fraction of an open position and proportionally releases collateral. PnL, funding, and the 10-bps taker fee are settled on the closed slice only.
client.partialClosePosition({
marketId: number,
closeBps: number, // 1..=9999. For a full close use close_position
})Errors: InvalidCloseBps (must be in [1, 9999]), OracleStale, MarketPaused, PoolInsufficient.
add_collateral
Deposits additional USDC into an open position. Resets anti-manipulation baseline.
client.addCollateral({ marketId: number, amount: BN })withdraw_collateral
Removes USDC from an open position; fails if the post-withdraw margin ratio would be ≤ 20%.
client.withdrawCollateral({ marketId: number, amount: BN })Errors: WithdrawalViolatesMargin, InsufficientCollateral, OracleStale.
Trigger Orders
place_trigger_order
Creates a SL, TP, or Limit Open order. For SL/TP, reduce_only must be true. For Limit Open, collateralAmount is escrowed in the global liquidity pool (earmarked to the TriggerOrder PDA), and the placement is gated by pool_token.amount ≥ pool.risk_floor. SL/TP placements have no risk_floor gate.
client.placeTriggerOrder({
marketId: number,
orderType: OrderType, // StopLoss (0) | TakeProfit (1) | LimitOpen (2)
direction: Direction,
triggerPrice: BN, // 10^6 scale
size: BN,
collateralAmount: BN,
leverage: number,
reduceOnly: boolean,
expiry: BN, // Unix seconds, 0 = never
})Errors: InvalidOrderType, InvalidTriggerCondition, MaxTriggerOrdersReached (10/market cap), ReduceOnlyRequiredForSLTP, OracleStale.
cancel_trigger_order
Owner cancels a pending order. For Limit Open, refunds escrowed collateral.
client.cancelTriggerOrder({ marketId: number, orderIdx: number })execute_trigger_close / execute_trigger_open
Permissionless. Keeper fires an eligible order.
client.executeTriggerClose({ marketId, owner, orderIdx })
client.executeTriggerOpen({ marketId, owner, orderIdx })Errors (both): TriggerConditionNotMet, OrderExpired, OrderAlreadyExecuted, PositionNotFound (for close), MarginTierViolation (for open).
Oracle & Funding
update_oracle
Pushes a new price, confidence interval, and live-match flag. Admin-only today; multi-source migration in progress.
client.updateOracle({
marketId: number,
price: BN, // 10^6 scale
confidenceBps: number, // e.g., 300 = 3%
isLive: boolean,
})Errors: Unauthorized, InvalidOraclePrice, MarketPaused.
apply_funding
Permissionless. Consumes accumulated premium samples (plain mean), clamps to ±10 bps, updates the cumulative funding counters.
client.applyFunding({ marketId })Liquidation (all permissionless)
partial_liquidate
Layer 1. Closes 20% of an underwater position. Liquidator earns 5%. 30s cooldown enforced.
client.partialLiquidate({ marketId, positionOwner })Errors: NotLiquidatable, NotEligibleForBackstop (below Layer 1 threshold), LiquidationCooldownActive, ProfitableLiquidation (anti-manipulation).
backstop_liquidate
Layer 2. Insurance fund absorbs position at mark price.
client.backstopLiquidate({ marketId, positionOwner })Errors: NotLiquidatable, InsuranceFundAtCapacity.
unwind_backstop
Layer 2. Gradually unwinds an absorbed position (max 10% per call). 3% reward.
client.unwindBackstop({ marketId, backstopSeq, maxAmount })auto_deleverage
Layer 3. Force-closes a profitable opposing position to cover an underwater one. Caller picks target.
client.autoDeleverage({ marketId, underwaterOwner, adlTargetOwner })Errors: ADLNotRequired (insurance could have absorbed), NoOpposingPositionsForADL (target not opposing side or not profitable).
liquidate (legacy)
Pre-Spec-1 full-close liquidation. Preserved for backwards compatibility. New integrations should use the layer-specific instructions.
Market Lifecycle
initialize_market (admin)
client.initializeMarket({
marketId, marketType, maxLeverage,
takerFeeBps, makerFeeBps, fundingInterval, maxFundingRateBps
}, usdcMint)pause_market / unpause_market (admin)
client.pauseMarket({ marketId })
client.unpauseMarket({ marketId })update_market_params (admin)
Adjusts oracle weights and vAMM impact factor.
client.updateMarketParams({
marketId,
oracleWeightLiveBps: number | null,
oracleWeightBetweenBps: number | null,
vammImpactFactor: number | null,
})sunset_market (permissionless)
Triggers if current_time - oracle_timestamp >= 72h.
client.sunsetMarket({ marketId })Errors: OracleNotStale (< 72h), AlreadySettled.
force_settle_market (admin)
Skip the 72h wait and settle at a specified price.
client.forceSettleMarket({ marketId, settlementPrice: BN })settle_position (permissionless)
Close any remaining position in a settled market at the fixed settlement price.
client.settlePosition({ marketId, positionOwner })Insurance Fund (admin-only except deposit)
initialize_insurance
One-time. Creates the global InsuranceFund PDA.
deposit_insurance
client.depositInsurance({ amount: BN })withdraw_insurance
Hard-capped: cannot leave total_balance < target_balance.
client.withdrawInsurance({ amount: BN })Errors: WithdrawalBelowTarget, InsufficientInsuranceBalance.
configure_insurance
client.configureInsurance({
maxBackstopExposure: BN,
targetBalance: BN,
})Liquidity Pool Admin
initialize_pool (admin)
One-time creation of the singleton LiquidityPool PDA + pool_token SPL token account. Sets risk_floor (hard on-chain gate; below it open_position and LimitOpen are blocked) and target_balance (off-chain alert threshold).
client.initializePool({ riskFloor: BN, targetBalance: BN })seed_pool (admin)
Deposits LP USDC into the global pool. Updates total_lp_deposits for accounting; pool_token.amount is the canonical balance.
client.seedPool({ amount: BN })withdraw_pool_lp (admin)
Withdraws LP USDC from the global pool. Gated: the pool’s post-withdraw token balance must remain at or above risk_floor. Updates total_lp_withdrawals.
client.withdrawPoolLp({ amount: BN })Errors: Unauthorized, RiskFloorBreached (pool_token.amount − amount < risk_floor), InsufficientPool.
Error code numbering
Anchor errors start at 6000 (offset 0) and increment. The full 47 codes are declared in programs/obv-perps/src/errors.rs. Never reused — retired codes stay retired (the legacy VaultInsufficient from the per-market-vault era is kept as a tombstone).
Common patterns:
| Code | Name | When it fires |
|---|---|---|
| 6001 | Unauthorized | Non-admin tried an admin instruction |
| 6002 | MarketPaused | Trading blocked for this market |
| 6003 | OracleStale | Oracle > 2h old |
| 6004 | InvalidOraclePrice | Price ≤ 0 |
| 6009 | NotLiquidatable | Margin ratio > 20% |
| 6043 | ProfitableLiquidation | Anti-manip check rejected |
(Codes above are illustrative; see errors.rs for exact numbering.)
IDL
The Anchor IDL (target/idl/obv_perps.json, ~120 KB) is committed in two places:
sdk/idl/obv_perps.json— consumed by the SDKapp/idl/obv_perps.json— consumed by the frontend
Any contributor can inspect the IDL directly or import it to hand-build custom instruction flows:
import IDL from "@sportsperp/sdk/idl/obv_perps.json";
const program = new Program(IDL as any, provider);
// program.methods.openPosition(...).accounts(...).rpc()Further reading
- SDK — client-level usage.
- Account Structure — what each account the instructions read/write looks like.
lib.rs— canonical declaration.