Skip to content

Limit Orders

Place an order that opens a position when the market reaches your price. Collateral is locked when the order is placed, and the order is executed by a keeper when the trigger condition is met.

openLimitOrderV2

solidity
function openLimitOrderV2(
    OpenDataInput memory data,
    OracleUpdateData calldata updateData,
    uint96 extraFee
) external payable returns (bytes32 orderHash);

Same OpenDataInput struct as a market order, with one difference:

FieldMarket orderLimit order
priceWorst acceptable fill priceThe limit price

Everything else — qty at 1e10, tokenIn/lvToken pairing, amountIn covering margin plus fee — behaves identically. broker and extraFee work exactly as they do for market orders; see Brokers & Referrals.

Oracle data is required at placement time even though the order does not fill immediately; it is used to validate the order against the current market.

Example

ts
import { parseUnits } from 'viem'
import { publicClient, walletClient, DIAMOND, USDC, LVUSD } from './config'
import { fetchOracleUpdate } from './oracle'

const OPEN_LIMIT_ABI = [{
  type: 'function',
  name: 'openLimitOrderV2',
  stateMutability: 'payable',
  inputs: [
    {
      name: 'data', type: 'tuple', components: [
        { name: 'pairBase', type: 'address' },
        { name: 'isLong', type: 'bool' },
        { name: 'tokenIn', type: 'address' },
        { name: 'lvToken', type: 'address' },
        { name: 'amountIn', type: 'uint96' },
        { name: 'qty', type: 'uint128' },
        { name: 'price', type: 'uint128' },
        { name: 'stopLoss', type: 'uint128' },
        { name: 'takeProfit', type: 'uint128' },
        { name: 'broker', type: 'uint24' },
      ],
    },
    {
      name: 'updateData', type: 'tuple', components: [
        { name: 'pythPriceUpdateData', type: 'bytes[]' },
        { name: 'pythProPriceUpdateData', type: 'bytes[]' },
      ],
    },
    { name: 'extraFee', type: 'uint96' },
  ],
  outputs: [{ name: 'orderHash', type: 'bytes32' }],
}] as const

// Buy $100 of BTC if it drops to $95,000, with a stop at $90,000.
const limitPrice = 95_000
const oracle = await fetchOracleUpdate(pairBase, USDC)

const hash = await walletClient.writeContract({
  address: DIAMOND,
  abi: OPEN_LIMIT_ABI,
  functionName: 'openLimitOrderV2',
  args: [
    {
      pairBase,
      isLong: true,
      tokenIn: USDC,
      lvToken: LVUSD,
      amountIn: parseUnits('10', 6),
      qty: parseUnits((100 / limitPrice).toFixed(10), 10),
      price: parseUnits(String(limitPrice), 18),
      stopLoss: parseUnits('90000', 18),
      takeProfit: 0n,
      broker: 0,
    },
    oracle.updateData,
    0n,
  ],
  value: oracle.value,
})

await publicClient.waitForTransactionReceipt({ hash })

The orderHash is emitted in the OpenLimitOrder event and returned by getLimitOrders.

Updating TP/SL

Adjust the take-profit and stop-loss that will be applied when the order fills. No oracle data needed.

solidity
function updateOrderTp(bytes32 orderHash, uint128 takeProfit) external;
function updateOrderSl(bytes32 orderHash, uint128 stopLoss) external;
function updateOrderTpAndSl(bytes32 orderHash, uint128 takeProfit, uint128 stopLoss) external;
ts
const UPDATE_ORDER_ABI = [{
  type: 'function',
  name: 'updateOrderTpAndSl',
  stateMutability: 'nonpayable',
  inputs: [
    { name: 'orderHash', type: 'bytes32' },
    { name: 'takeProfit', type: 'uint128' },
    { name: 'stopLoss', type: 'uint128' },
  ],
  outputs: [],
}] as const

await walletClient.writeContract({
  address: DIAMOND,
  abi: UPDATE_ORDER_ABI,
  functionName: 'updateOrderTpAndSl',
  args: [orderHash, parseUnits('120000', 18), parseUnits('90000', 18)],
})

Pass 0 to clear a stop-loss.

Cancelling

solidity
function cancelLimitOrder(bytes32 orderHash) external;
function batchCancelLimitOrders(bytes32[] calldata orderHashes) external;

Cancelling returns the locked collateral. No oracle data needed.

ts
const CANCEL_ABI = [
  {
    type: 'function',
    name: 'cancelLimitOrder',
    stateMutability: 'nonpayable',
    inputs: [{ name: 'orderHash', type: 'bytes32' }],
    outputs: [],
  },
  {
    type: 'function',
    name: 'batchCancelLimitOrders',
    stateMutability: 'nonpayable',
    inputs: [{ name: 'orderHashs', type: 'bytes32[]' }],
    outputs: [],
  },
] as const

await walletClient.writeContract({
  address: DIAMOND,
  abi: CANCEL_ABI,
  functionName: 'batchCancelLimitOrders',
  args: [[orderHashA, orderHashB]],
})

Order lifecycle

EventMeaning
OpenLimitOrderOrder placed, collateral locked
ExecuteLimitOrderSuccessfulTrigger hit, position opened
ExecuteLimitOrderRejectedTrigger hit but the open failed validation; order stays or is refunded
LimitOrderRefundCollateral returned
CancelLimitOrderCancelled by the trader
UpdateOrderTp / UpdateOrderSlTP/SL changed

See Events for signatures, and Reading Data for querying live orders.

Trading perpetuals involves risk. Nothing here is financial advice.