🚧 SportsPerp is currently live on devnet. Mainnet target: before Jun 12, 2026 (World Cup kickoff).
For DevelopersProgram Instructions Reference

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:

CodeNameWhen it fires
6001UnauthorizedNon-admin tried an admin instruction
6002MarketPausedTrading blocked for this market
6003OracleStaleOracle > 2h old
6004InvalidOraclePricePrice ≤ 0
6009NotLiquidatableMargin ratio > 20%
6043ProfitableLiquidationAnti-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:

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.