Skip to content

Add split receive option to TWAP decrease and TP/SL order creation FEDEV-3918#2580

Open
qwinndev wants to merge 13 commits into
releasefrom
split-receive-twap-tpsl
Open

Add split receive option to TWAP decrease and TP/SL order creation FEDEV-3918#2580
qwinndev wants to merge 13 commits into
releasefrom
split-receive-twap-tpsl

Conversation

@qwinndev

@qwinndev qwinndev commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Extends the split-receive option (return PnL token + collateral token separately, with no internal PnL→collateral swap) — already shipped for plain close/decrease in FEDEV-3460 — to two more flows:

  • TWAP decrease orders (PositionSeller close modal, TWAP mode)
  • Standalone TP/SL orders (AddTPSLModal)

For markets where collateral and PnL tokens differ (e.g. a WBTC-backed long paying PnL in WBTC), users can now avoid the forced internal swap — and its swap-profit fee — on every scheduled fill, and see the choice up front.

This effectively restores the TWAP/TP-SL split-receive that was added and then reverted within the FEDEV-3460 PR (#2500, revert 4aa45475e5), adapted to current code.

What changed

  • No SDK changes. The order payload already persists decreasePositionSwapType per TWAP leg (orderTransactions/utils.ts) and per TP/SL order (sidecar.ts), so the receive preference is honored on every scheduled fill automatically — setting decreaseAmounts.decreaseSwapType = NoSwap is all that's needed.
  • TWAP decrease (PositionSeller.tsx, positionSellerSelectors.ts): removed the two TradeMode.Twap guards, un-gated the split toggle and auto-clear effect, and added a read-only Receive row (Option A — no token selector for TWAP).
  • Standalone TP/SL (AddTPSLModal.tsx): re-added the isReceiveSeparated state, forceDecreaseSwapType: NoSwap amounts, split-receive availability, output preview, and toggle. Submit path unchanged.
  • Warning: extracted a shared getIsHighSwapProfitFee helper from usePriceImpactWarningState (behavior-preserving) and use it to gate a dedicated "high swap profit fee → use split receive" AlertInfoCard in both new flows.
  • Standalone flows keep single-token receive by default; inline tradebox TP/SL orders now default to split receive when collateral ≠ PnL token (silent, no new UI). Reuses existing components and copy → no new locale strings.

FEDEV-3918

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 24, 2026

Copy link
Copy Markdown

Deploying gmx-interface with  Cloudflare Pages  Cloudflare Pages

Latest commit: d155f60
Status: ✅  Deploy successful!
Preview URL: https://84fc626e.gmx-interface.pages.dev
Branch Preview URL: https://split-receive-twap-tpsl.gmx-interface.pages.dev

View logs

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 24, 2026

Copy link
Copy Markdown

Deploying gmx-interface-home with  Cloudflare Pages  Cloudflare Pages

Latest commit: d155f60
Status: ✅  Deploy successful!
Preview URL: https://55c15e69.gmx-interface-home.pages.dev
Branch Preview URL: https://split-receive-twap-tpsl.gmx-interface-home.pages.dev

View logs

if (slTriggerPrice === undefined || !getIsPositionInfoLoaded(position)) {
return undefined;
}
const receiveSwapType = isReceiveSeparated ? DecreasePositionSwapType.NoSwap : undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This choice doesn't survive the replace path. buildTpSlBatchPayloads replaces an existing full-close order via buildUpdateOrderPayload (sidecar.ts:310), and updateOrder can't change decreasePositionSwapType — it only carries size/prices/minOutput/autoCancel/validFrom. The modal defaults to 100% size, so isFullClose is almost always true, and the typical flow becomes: user has an existing full-close TP (created with the swap default), re-opens the modal, sets a new price, toggles "Receive X and Y separately" — the preview shows the split, but the submitted update keeps the old order's swap type, and execution swaps the profit anyway. The reverse holds too: an inline-created NoSwap TP edited without the toggle previews single-token receive but executes as split.

A preview contradicting the actual payload in a money modal is the bad kind of quiet. Since update can't carry the field, the replace path needs cancel+create whenever the swap type differs — the batch already supports cancelOrderParams (currently hardcoded [] here).

triggerOrderType: OrderType,
position: PositionInfo | undefined
): DecreasePositionSwapType | undefined {
if (triggerOrderType === OrderType.LimitDecrease && getCanSplitReceive(position)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Jun 26 follow-up on the Linear issue asks for inline TP/SL to default to split receive — this only covers TP (LimitDecrease). An SL isn't guaranteed to execute at a loss: stop-in-profit above entry is a normal setup, and there the profit still gets force-swapped — exactly the "unexpected internal conversion" that follow-up wants to rule out. For a losing fill NoSwap is equivalent anyway (no profit to swap), so including StopLossDecrease changes nothing else.

If TP-only is a deliberate product call, let's record it on the Linear issue and in a comment here. Either way the PR description still says "Default behavior is unchanged" — worth syncing it with this change.

swapProfitFee: fees?.swapProfitFee,
});

const twapSplitReceiveSwapProfitFeeWarning = getSplitReceiveSwapProfitFeeWarning({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

receiveToken is hardcoded to collateral here, but the actual TWAP legs are built from selectPositionSellerReceiveToken, which can still return a token picked on the Market tab this session or the persisted defaultReceiveToken from localStorage (saved per market+direction+collateral on every successful close). So a user who once closed this position into BTC gets TWAP legs carrying a USDC→BTC swapPath, while the new receive row shows collateral amounts and this warning talks about "Receiving as USDC". Execution was the same before the PR — but TWAP didn't render a Receive row at all, so the UI couldn't contradict the payload; now it can.

Since TWAP deliberately has no receive selector and all the new UI assumes collateral, I'd pin the effective receive token to collateral for TWAP in the selector itself — that fixes the optimal-decrease pick, swapPath, batch params and this copy in one place.

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

Playwright Component Test Report

All tests passed.

View Report

Commit d155f60

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants