Skip to content

Managing a Position

Adjust an open position without closing it: change leverage by moving margin, or move the take-profit and stop-loss.

Add margin

Deposit more collateral into an open position, lowering its leverage and moving the liquidation price away.

solidity
function addMargin(bytes32 tradeHash, address tokenIn, uint96 amount) external payable;

No oracle data required.

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

const ADD_MARGIN_ABI = [{
  type: 'function',
  name: 'addMargin',
  stateMutability: 'payable',
  inputs: [
    { name: 'tradeHash', type: 'bytes32' },
    { name: 'tokenIn', type: 'address' },
    { name: 'amount', type: 'uint96' },
  ],
  outputs: [],
}] as const

const hash = await walletClient.writeContract({
  address: DIAMOND,
  abi: ADD_MARGIN_ABI,
  functionName: 'addMargin',
  args: [positionHash, USDC, parseUnits('5', 6)],
})
await publicClient.waitForTransactionReceipt({ hash })

tokenIn must be compatible with the position's lvToken — you cannot add MON to a position settling in LVUSD. For native MON, pass the zero address and send the amount as value:

ts
await walletClient.writeContract({
  address: DIAMOND,
  abi: ADD_MARGIN_ABI,
  functionName: 'addMargin',
  args: [positionHash, '0x0000000000000000000000000000000000000000', amount],
  value: amount,
})

Remove margin

Withdraw collateral from an open position, raising its leverage.

solidity
function removeMargin(
    bytes32 tradeHash,
    uint96 lvAmount,
    OracleUpdateData calldata updateData
) external payable;

Unlike addMargin, this requires oracle data — the protocol must value the position before letting collateral out. msg.value must cover the oracle update fee.

Note that lvAmount is denominated in the position's lvToken (18 decimals for both LVUSD and LVMON), not in tokenIn. A position funded with USDC still reports and withdraws margin at 18 decimals.

ts
import { parseUnits } from 'viem'
import { fetchOracleUpdate } from './oracle'

const REMOVE_MARGIN_ABI = [{
  type: 'function',
  name: 'removeMargin',
  stateMutability: 'payable',
  inputs: [
    { name: 'tradeHash', type: 'bytes32' },
    { name: 'lvAmount', type: 'uint96' },
    {
      name: 'updateData', type: 'tuple', components: [
        { name: 'pythPriceUpdateData', type: 'bytes[]' },
        { name: 'pythProPriceUpdateData', type: 'bytes[]' },
      ],
    },
  ],
  outputs: [],
}] as const

const oracle = await fetchOracleUpdate(pairBase, USDC)

await walletClient.writeContract({
  address: DIAMOND,
  abi: REMOVE_MARGIN_ABI,
  functionName: 'removeMargin',
  args: [positionHash, parseUnits('2', 18), oracle.updateData],   // 2 LVUSD
  value: oracle.value,
})

The protocol rejects a removal that would leave the position in a bad state:

ErrorMeaning
ExceedMaxLeverageAfterMarginRemovalResulting leverage above the pair's maximum
LiquidatableAfterMarginRemovalPosition would be immediately liquidatable
ExceedMaxTakeProfitAfterMarginRemovalExisting take-profit would exceed its allowed bound at the new leverage
InsufficientMarginAmountlvAmount exceeds free margin

Both operations emit UpdateMargin(user, tradeHash, beforeMargin, margin).

Take-profit and stop-loss

solidity
function updateTradeTp(bytes32 tradeHash, uint128 takeProfit) external;
function updateTradeSl(bytes32 tradeHash, uint128 stopLoss) external;
function updateTradeTpAndSl(bytes32 tradeHash, uint128 takeProfit, uint128 stopLoss) external;

Prices are 1e18. Passing 0 for stopLoss removes it. No oracle data required — these only write stored trigger values.

ts
const UPDATE_TPSL_ABI = [{
  type: 'function',
  name: 'updateTradeTpAndSl',
  stateMutability: 'nonpayable',
  inputs: [
    { name: 'tradeHash', type: 'bytes32' },
    { name: 'takeProfit', type: 'uint128' },
    { name: 'stopLoss', type: 'uint128' },
  ],
  outputs: [],
}] as const

await walletClient.writeContract({
  address: DIAMOND,
  abi: UPDATE_TPSL_ABI,
  functionName: 'updateTradeTpAndSl',
  args: [positionHash, parseUnits('120000', 18), parseUnits('90000', 18)],
})

Take-profit is bounded by the position's leverage — an unreachably high target is clamped or rejected. Stop-loss must be on the loss side of the entry price and is validated on write (InvalidStopLoss).

Emits UpdateTradeTp and UpdateTradeSl with before/after values.

When to use TP/SL orders instead

These functions set a single take-profit and a single stop-loss on the position itself. If you need partial take-profits — scaling out at several price levels — use TP/SL orders, which support multiple legs per position.

The two mechanisms coexist. Legacy positions support only the embedded fields.

Trading perpetuals involves risk. Nothing here is financial advice.