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.
function addMargin(bytes32 tradeHash, address tokenIn, uint96 amount) external payable;No oracle data required.
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:
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.
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.
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:
| Error | Meaning |
|---|---|
ExceedMaxLeverageAfterMarginRemoval | Resulting leverage above the pair's maximum |
LiquidatableAfterMarginRemoval | Position would be immediately liquidatable |
ExceedMaxTakeProfitAfterMarginRemoval | Existing take-profit would exceed its allowed bound at the new leverage |
InsufficientMarginAmount | lvAmount exceeds free margin |
Both operations emit UpdateMargin(user, tradeHash, beforeMargin, margin).
Take-profit and stop-loss
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.
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.