Skip to content

Closing & Partial Close

Closing does not require oracle data — the keeper supplies the settlement price. All close functions are on the Diamond.

Full close

solidity
function closeTrade(bytes32 positionHash) external;
function closeTrade(bytes32 positionHash, uint24 broker) external;

The two-argument form credits the close fee to a broker id. Use 0 or the single-argument form if you do not operate a broker integration.

The close broker is independent of the open broker

The broker recorded when the position was opened does not carry over. Whatever id you pass here receives the close-fee share — and the single-argument form credits the default broker. A broker integration must pass its id on the close as well as the open. See Brokers & Referrals.

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

const CLOSE_ABI = [{
  type: 'function',
  name: 'closeTrade',
  stateMutability: 'nonpayable',
  inputs: [{ name: 'positionHash', type: 'bytes32' }],
  outputs: [],
}] as const

const hash = await walletClient.writeContract({
  address: DIAMOND,
  abi: CLOSE_ABI,
  functionName: 'closeTrade',
  args: [positionHash],
})
await publicClient.waitForTransactionReceipt({ hash })

WARNING

Solidity overloads share a name. If you put every closeTrade variant into one ABI array, viem cannot tell them apart — pass only the fragment you intend to call, or disambiguate explicitly.

A full close also cancels every TP/SL order attached to that position, each emitting DecreaseOrderCancelled.

Partial close

solidity
function closeTrade(bytes32 positionHash, uint128 closeQty, uint24 broker) external;

Closes closeQty of the position and leaves the rest open. The remainder keeps its entry price, and accrued funding and holding fees are split proportionally.

ts
const PARTIAL_CLOSE_ABI = [{
  type: 'function',
  name: 'closeTrade',
  stateMutability: 'nonpayable',
  inputs: [
    { name: 'positionHash', type: 'bytes32' },
    { name: 'closeQty', type: 'uint128' },
    { name: 'broker', type: 'uint24' },
  ],
  outputs: [],
}] as const

// Close half of the position.
await walletClient.writeContract({
  address: DIAMOND,
  abi: PARTIAL_CLOSE_ABI,
  functionName: 'closeTrade',
  args: [positionHash, position.qty / 2n, 0],
})
closeQtyResult
0Full close
>= position.qtyFull close
BetweenPartial close, remainder stays open

closeQty uses the same 1e10 scale as qty — see Precision & Units.

Merged positions only

Partial close requires a position with a deterministic key. Legacy positions created before the merged-position upgrade are full-close only, and a partial request against one reverts. See Positions are merged slots.

Batch close

solidity
function batchCloseTrade(bytes32[] calldata positionHashes) external;
function batchCloseTrade(bytes32[] calldata positionHashes, uint24 broker) external;
ts
const BATCH_CLOSE_ABI = [{
  type: 'function',
  name: 'batchCloseTrade',
  stateMutability: 'nonpayable',
  inputs: [{ name: 'positionHashes', type: 'bytes32[]' }],
  outputs: [],
}] as const

await walletClient.writeContract({
  address: DIAMOND,
  abi: BATCH_CLOSE_ABI,
  functionName: 'batchCloseTrade',
  args: [positions.map((p) => p.positionHash)],
})

Finding the position hash

Three ways, in order of convenience:

ts
// 1. Compute it — no RPC call needed.
import { keccak256, encodeAbiParameters } from 'viem'

const positionHash = keccak256(
  encodeAbiParameters(
    [{ type: 'address' }, { type: 'address' }, { type: 'bool' }, { type: 'address' }, { type: 'string' }],
    [user, pairBase, isLong, lvToken, 'position.v1'],
  ),
)

// 2. Read it from the contract — getPositionHash(user, pairBase, isLong, lvToken)
// 3. Read open positions — getPositionsV4(user, pairBase)

See Reading Data.

After sending

Closing is a request. The keeper settles it and emits:

EventMeaning
CloseTradeRequestedYour transaction was accepted
ClosePositionPosition fully closed and settled
PositionDecreasedPartial close settled, remainder still open
ExecuteCloseRejectedClose could not be settled

ClosePosition and PositionDecreased carry a CloseInfo with the realized numbers:

solidity
struct CloseInfo {
    uint128 closePrice;  // 1e18
    int96 fundingFee;    // lvToken decimals, signed
    uint96 closeFee;     // lvToken decimals
    int96 pnl;           // lvToken decimals, signed
    uint96 holdingFee;   // lvToken decimals
}

Common reverts

ErrorCause
NonexistentTradePosition already closed, or wrong hash
PositionNotCloseableMinimum holding period not elapsed — check earliestCloseTime
MarketClosedMarket outside trading hours
CoolingOffPeriodAction temporarily unavailable

Full list: Error Reference.

Trading perpetuals involves risk. Nothing here is financial advice.