Closing & Partial Close
Closing does not require oracle data — the keeper supplies the settlement price. All close functions are on the Diamond.
Full close
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.
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
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.
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],
})closeQty | Result |
|---|---|
0 | Full close |
>= position.qty | Full close |
| Between | Partial 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
function batchCloseTrade(bytes32[] calldata positionHashes) external;
function batchCloseTrade(bytes32[] calldata positionHashes, uint24 broker) external;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:
// 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:
| Event | Meaning |
|---|---|
CloseTradeRequested | Your transaction was accepted |
ClosePosition | Position fully closed and settled |
PositionDecreased | Partial close settled, remainder still open |
ExecuteCloseRejected | Close could not be settled |
ClosePosition and PositionDecreased carry a CloseInfo with the realized numbers:
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
| Error | Cause |
|---|---|
NonexistentTrade | Position already closed, or wrong hash |
PositionNotCloseable | Minimum holding period not elapsed — check earliestCloseTime |
MarketClosed | Market outside trading hours |
CoolingOffPeriod | Action temporarily unavailable |
Full list: Error Reference.