Skip to main content

DispatchFrom Entry Point

The dispatchFrom function enables third parties to interact with another user's positions for liquidations, force exercises, and long premium settlements. The specific operation is determined by the account's solvency state and the relationship between input position lists.

Overview
​

function dispatchFrom(
TokenId[] calldata positionIdListFrom,
address account,
TokenId[] calldata positionIdListTo,
TokenId[] calldata positionIdListToFinal,
LeftRightUnsigned usePremiaAsCollateral
) external payable

Parameters
​

ParameterDescription
positionIdListFromCaller's (msg.sender) current positions
accountTarget account being acted upon
positionIdListToTarget account's current positions
positionIdListToFinalExpected positions after operation
usePremiaAsCollateralPacked flags: leftSlot for caller, rightSlot for target

Operation Determination
​

The function determines which operation to execute based on two factors:

  1. Solvency State: Is the target account solvent at all checked price ticks?
  2. List Lengths: How do positionIdListTo and positionIdListToFinal compare?

Solvency Check
​

The account is checked at four price points:

int24[] memory atTicks = new int24[](4);
atTicks[0] = spotTick; // 10-minute EMA
atTicks[1] = twapTick; // Weighted TWAP from oracle
atTicks[2] = latestTick; // Most recent observation
atTicks[3] = currentTick; // Current Uniswap tick

solvent = _checkSolvencyAtTicks(
account,
0, // No safe mode override
positionIdListTo,
currentTick,
atTicks,
COMPUTE_PREMIA_AS_COLLATERAL,
NO_BUFFER // No additional margin buffer
);

Decision Matrix
​

Solvent CountFinal LengthTo LengthOperation
4 (all)= To LengthN/ASettle Premium
4 (all)To Length - 1N/AForce Exercise
0 (none)0N/ALiquidation
1-3 (partial)AnyAnyRevert (NotMarginCalled)
if (solvent == numberOfTicks) {
// Solvent at all ticks
if (toLength == finalLength) {
_settlePremium(...); // Same length = settle
} else if (toLength == finalLength + 1) {
_forceExercise(...); // One shorter = exercise
} else if (finalLength == 0) {
revert Errors.NotMarginCalled(); // Was meant for liquidation but solvent
}
} else if (solvent == 0) {
// Insolvent at all ticks
if (finalLength != 0) revert Errors.InputListFail();
_liquidate(...);
} else {
// Partially solvent - can't proceed
revert Errors.NotMarginCalled();
}

Price Manipulation Protection
​

Before any operation, the function validates that the current price hasn't been manipulated:

int256 MAX_TWAP_DELTA_LIQUIDATION = int256(uint256(riskParameters.tickDeltaLiquidation()));

if (Math.abs(currentTick - twapTick) > MAX_TWAP_DELTA_LIQUIDATION)
revert Errors.StaleOracle();

This prevents attackers from:

  • Manipulating the current price to trigger unfair liquidations
  • Executing force exercises at manipulated prices

Post-Operation Validation
​

Both the target account and caller must remain solvent:

// Validate target account (after operation)
_validateSolvency(
account,
positionIdListToFinal,
NO_BUFFER,
usePremiaAsCollateral.rightSlot() > 0,
0
);

// Validate caller
_validateSolvency(
msg.sender,
positionIdListFrom,
NO_BUFFER,
usePremiaAsCollateral.leftSlot() > 0,
0
);

Liquidation Flow

Liquidation occurs when an account is insolvent at all four checked price ticks. All positions are closed and a bonus is paid to the liquidator.

Function Signature
​

function _liquidate(
address liquidatee,
TokenId[] calldata positionIdList,
int24 twapTick,
int24 currentTick
) internal

Flow Diagram
​

_liquidate
β”‚
β”œβ”€β–Ί Calculate accumulated premia
β”‚ └─ _calculateAccumulatedPremia(liquidatee, positionIdList, ...)
β”‚
β”œβ”€β–Ί Get margin data from RiskEngine
β”‚ └─ riskEngine().getMargin(...)
β”‚
β”œβ”€β–Ί Delegate virtual shares to liquidatee
β”‚ β”œβ”€ collateralToken0().delegate(liquidatee)
β”‚ └─ collateralToken1().delegate(liquidatee)
β”‚
β”œβ”€β–Ί Burn all positions (without committing long premium)
β”‚ └─ _burnAllOptionsFrom(liquidatee, ..., DONOT_COMMIT_LONG_SETTLED, ...)
β”‚
β”œβ”€β–Ί Calculate liquidation bonus
β”‚ └─ riskEngine().getLiquidationBonus(...)
β”‚
β”œβ”€β–Ί Process premium haircut (if protocol loss)
β”‚ └─ PanopticMath.haircutPremia(...)
β”‚
β”œβ”€β–Ί Settle with liquidator
β”‚ β”œβ”€ collateralToken0().settleLiquidation(liquidator, liquidatee, bonus0)
β”‚ └─ collateralToken1().settleLiquidation(liquidator, liquidatee, bonus1)
β”‚
└─► Emit AccountLiquidated event

Key Steps
​

1. Premium Calculation
​

(shortPremium, longPremium, positionBalanceArray) = _calculateAccumulatedPremia(
liquidatee,
positionIdList,
COMPUTE_PREMIA_AS_COLLATERAL,
ONLY_AVAILABLE_PREMIUM, // Only settled premium counts
currentTick
);

2. Virtual Share Delegation
​

The protocol delegates virtual shares to ensure the liquidatee has enough balance to settle all position closures:

collateralToken0().delegate(liquidatee);  // Adds 2^248 - 1 shares
collateralToken1().delegate(liquidatee);

This is necessary because the liquidatee may not have enough shares to cover the settlement amounts for burning their positions.

3. Position Burning
​

(netPaid, premiasByLeg) = _burnAllOptionsFrom(
liquidatee,
MIN_SWAP_TICK, // No price limits during liquidation
MAX_SWAP_TICK,
DONOT_COMMIT_LONG_SETTLED, // Don't commit long premium yet
positionIdList
);

The DONOT_COMMIT_LONG_SETTLED flag prevents long premium from being committed to storage. This is critical because:

  • The premium may need to be haircut if there's protocol loss
  • Committing first could allow shorts to withdraw tokens that will later be clawed back

4. Bonus Calculation
​

(bonusAmounts, collateralRemaining) = riskEngine().getLiquidationBonus(
tokenData0,
tokenData1,
Math.getSqrtRatioAtTick(twapTick),
netPaid,
shortPremium
);

The bonus is calculated as:

  • min(collateralBalance/2, collateralDeficit)
  • Split proportionally between token0 and token1 based on requirements
  • Cross-token substitution applied if one token has surplus

5. Premium Haircut
​

If there's protocol loss, premium owed to the liquidatee is haircut:

LeftRightSigned bonusDeltas = PanopticMath.haircutPremia(
liquidatee,
positionIdList,
premiasByLeg,
collateralRemaining,
collateralToken0(),
collateralToken1(),
Math.getSqrtRatioAtTick(twapTick),
s_settledTokens
);
bonusAmounts = bonusAmounts.add(bonusDeltas);

This ensures PLPs aren't forced to pay out premium to a liquidator who colluded with the liquidatee.

6. Settlement
​

// Native currency support for token0
collateralToken0().settleLiquidation{value: msg.value}(
msg.sender, // liquidator
liquidatee,
bonusAmounts.rightSlot()
);

collateralToken1().settleLiquidation(
msg.sender,
liquidatee,
bonusAmounts.leftSlot()
);

The settleLiquidation function:

  • Revokes the delegated virtual shares
  • Transfers bonus to liquidator (or from liquidator if negative)
  • Handles any protocol loss

Force Exercise Flow

Force exercise allows anyone to close another user's out-of-range long positions in exchange for paying an exercise fee.

Function Signature
​

function _forceExercise(
address account,
TokenId tokenId,
int24 twapTick,
int24 currentTick
) internal

Prerequisites
​

// Position must have at least one long leg
if (tokenId.countLongs() == 0) revert Errors.NoLegsExercisable();

Flow Diagram
​

_forceExercise
β”‚
β”œβ”€β–Ί Get position data and calculate exercise fee
β”‚ β”œβ”€ positionSize = s_positionBalance[account][tokenId].positionSize()
β”‚ └─ exerciseFees = riskEngine().exerciseCost(currentTick, twapTick, tokenId, positionBalance)
β”‚
β”œβ”€β–Ί Delegate virtual shares to account
β”‚ β”œβ”€ collateralToken0().delegate(account)
β”‚ └─ collateralToken1().delegate(account)
β”‚
β”œβ”€β–Ί Burn the position
β”‚ └─ _burnOptions(tokenId, positionSize, [MIN_SWAP_TICK, MAX_SWAP_TICK], account, COMMIT_LONG_SETTLED, ...)
β”‚
β”œβ”€β–Ί Calculate refund amounts (handle token imbalances)
β”‚ └─ riskEngine().getRefundAmounts(account, exerciseFees, twapTick, ct0, ct1)
β”‚
β”œβ”€β–Ί Execute refunds between exerciser and account
β”‚ β”œβ”€ collateralToken0().refund(account, msg.sender, refundAmounts.rightSlot())
β”‚ └─ collateralToken1().refund(account, msg.sender, refundAmounts.leftSlot())
β”‚
β”œβ”€β–Ί Revoke virtual shares
β”‚ β”œβ”€ collateralToken0().revoke(account)
β”‚ └─ collateralToken1().revoke(account)
β”‚
└─► Emit ForcedExercised event

Key Steps
​

1. Exercise Fee Calculation
​

exerciseFees = riskEngine().exerciseCost(
currentTick,
twapTick,
tokenId,
positionBalance
);

The exercise cost includes:

  • Base fee (higher if position is in-range, lower if far OTM)
  • Price differential compensation between current and oracle prices

2. Position Burning
​

int24[2] memory tickLimits;
tickLimits[0] = MIN_SWAP_TICK; // No ITM swapping
tickLimits[1] = MAX_SWAP_TICK;

_burnOptions(
tokenId,
positionSize,
tickLimits,
account,
COMMIT_LONG_SETTLED, // Commit premium (unlike liquidation)
riskParameters
);

Unlike liquidation, force exercise does commit long premium because:

  • The account is solvent
  • No protocol loss risk
  • Normal premium flow should occur

3. Refund Amount Calculation
​

LeftRightSigned refundAmounts = riskEngine().getRefundAmounts(
account,
exerciseFees,
twapTick,
ct0,
ct1
);

If the exercised account lacks sufficient balance in one token:

  • The deficit is converted to the other token
  • Exerciser receives equivalent value in the surplus token
  • Ensures the exercise can complete even with imbalanced holdings

4. Token Transfers
​

// Positive = transfer from account to exerciser
// Negative = transfer from exerciser to account
ct0.refund(account, msg.sender, refundAmounts.rightSlot());
ct1.refund(account, msg.sender, refundAmounts.leftSlot());

Settle Premium Flow

Premium settlement allows third parties to force long position holders to pay their accumulated premium, making it available for short sellers to withdraw.

Function Signature
​

function _settlePremium(
address owner,
TokenId tokenId,
int24 twapTick,
int24 currentTick
) internal

Flow Diagram
​

_settlePremium
β”‚
β”œβ”€β–Ί Delegate virtual shares to owner
β”‚ β”œβ”€ collateralToken0().delegate(owner)
β”‚ └─ collateralToken1().delegate(owner)
β”‚
β”œβ”€β–Ί Settle options (keep position open)
β”‚ └─ _settleOptions(owner, tokenId, positionSize, riskParameters, currentTick)
β”‚
β”œβ”€β–Ί Calculate refund amounts
β”‚ └─ riskEngine().getRefundAmounts(owner, LeftRightSigned.wrap(0), twapTick, ct0, ct1)
β”‚
β”œβ”€β–Ί Execute refunds (caller pays for owner's shortfall)
β”‚ β”œβ”€ collateralToken0().refund(owner, msg.sender, refundAmounts.rightSlot())
β”‚ └─ collateralToken1().refund(owner, msg.sender, refundAmounts.leftSlot())
β”‚
└─► Revoke virtual shares
β”œβ”€ collateralToken0().revoke(owner)
└─ collateralToken1().revoke(owner)

Purpose
​

Short sellers need long buyers to pay their accumulated premium before the shorts can withdraw their earnings. If a long holder is neglecting to settle:

  1. Any third party can call dispatchFrom to force settlement
  2. The long's premium debt is paid
  3. s_settledTokens is increased, making premium available to shorts
  4. The long position remains open

Key Steps
​

1. Position Validation
​

uint128 positionSize = s_positionBalance[owner][tokenId].positionSize();
if (positionSize == 0) revert Errors.PositionNotOwned();

2. Premium Settlement
​

_settleOptions(owner, tokenId, positionSize, riskParameters, currentTick);

This calls _updateSettlementPostBurn with flags to:

  • Commit long premium to s_settledTokens
  • Keep the position open
  • Update premium accumulator snapshots

3. Balance Redistribution
​

LeftRightSigned refundAmounts = riskEngine().getRefundAmounts(
owner,
LeftRightSigned.wrap(0), // No exercise fees
twapTick,
ct0,
ct1
);

If the owner lacks sufficient collateral in one token:

  • The caller covers the shortfall
  • Caller receives equivalent value in the other token
  • This incentivizes settlement when the owner has imbalanced collateral

Summary

OperationTriggerAccount StatePosition Effect
LiquidationInsolvent at all ticksInsolventAll positions closed
Force ExerciseFinal list one shorterSolventSingle position closed
Settle PremiumSame list lengthsSolventPosition remains open
OperationCaller PaysCaller ReceivesAccount Effect
LiquidationNothing (or negative bonus)Liquidation bonusPositions closed, collateral claimed
Force ExerciseExercise feePosition closurePosition closed, receives fee
Settle PremiumToken shortfallSurplus tokenPremium debt paid