diff --git a/packages/kit/src/views/Perp/components/TradingPanel/panels/PerpTradingForm.tsx b/packages/kit/src/views/Perp/components/TradingPanel/panels/PerpTradingForm.tsx
index 821e05622ca..19f5635b9cc 100644
--- a/packages/kit/src/views/Perp/components/TradingPanel/panels/PerpTradingForm.tsx
+++ b/packages/kit/src/views/Perp/components/TradingPanel/panels/PerpTradingForm.tsx
@@ -8,6 +8,7 @@ import {
NumberSizeableText,
SizableText,
Skeleton,
+ Slider,
XStack,
YStack,
getFontSize,
@@ -98,6 +99,8 @@ function PerpTradingForm({
updateForm,
]);
+ // Token Switch Effect: Handle price updates when user switches tokens
+ // This prevents stale price data from being used during token transitions
useEffect(() => {
const prevToken = prevTokenRef.current;
const hasTokenChanged =
@@ -110,14 +113,14 @@ function PerpTradingForm({
activeAssetCtx?.ctx?.markPrice &&
isDataSynced;
- // Handle token switch
+ // Step 1: Detect token switch and mark switching state
if (hasTokenChanged) {
tokenSwitchingRef.current = currentTokenName;
prevTokenRef.current = currentTokenName;
return; // Early return to avoid price update with stale data
}
- // Update price after token switch when data is synchronized
+ // Step 2: Update price after token data is synchronized (prevents stale price)
if (shouldUpdatePrice && activeAssetCtx?.ctx?.markPrice) {
updateForm({
price: formatPriceToSignificantDigits(activeAssetCtx?.ctx?.markPrice),
@@ -125,6 +128,7 @@ function PerpTradingForm({
tokenSwitchingRef.current = false;
}
+ // Step 3: Initialize token reference on first load
if (!prevToken && currentTokenName) {
prevTokenRef.current = currentTokenName;
}
@@ -141,6 +145,7 @@ function PerpTradingForm({
);
}, [activeAssetData?.leverage?.value, activeAsset?.universe?.maxLeverage]);
+ // Reference Price: Get the effective trading price (limit price or market price)
const [referencePrice, referencePriceString] = useMemo(() => {
let price = new BigNumber(0);
if (formData.type === 'limit' && formData.price) {
@@ -163,10 +168,14 @@ function PerpTradingForm({
activeAsset?.universe?.szDecimals,
]);
- const availableToTrade = useMemo(() => {
+ const { availableToTradeDisplay, availableToTradeValue } = useMemo(() => {
const _availableToTrade = activeAssetData?.availableToTrade || [0, 0];
const value = _availableToTrade[formData.side === 'long' ? 0 : 1] || 0;
- return new BigNumber(value).toFixed(2, BigNumber.ROUND_DOWN);
+ const valueBN = new BigNumber(value);
+ return {
+ availableToTradeDisplay: valueBN.toFixed(2, BigNumber.ROUND_DOWN),
+ availableToTradeValue: valueBN.toNumber(),
+ };
}, [formData.side, activeAssetData?.availableToTrade]);
const [selectedSymbolPositionValue, selectedSymbolPositionSide] =
@@ -181,17 +190,100 @@ function PerpTradingForm({
return [Math.abs(value), side];
}, [perpsPositions, perpsSelectedSymbol.coin]);
+ // Order calculations: Total value and required margin
const totalValue = useMemo(() => {
const size = new BigNumber(formData.size || 0);
- return size.multipliedBy(referencePrice);
+ return size.multipliedBy(referencePrice); // Size × Price = Total Value
}, [formData.size, referencePrice]);
const marginRequired = useMemo(() => {
return new BigNumber(formData.size || 0)
.multipliedBy(referencePrice)
- .dividedBy(leverage || 1);
+ .dividedBy(leverage || 1); // (Size × Price) ÷ Leverage = Required Margin
}, [formData.size, referencePrice, leverage]);
+ // Slider Configuration: Calculate price, leverage, max value and current value for size slider
+ const sliderConfig = useMemo(() => {
+ // Get effective price for slider calculation (limit price or market price)
+ const getEffectivePrice = (): BigNumber | null => {
+ if (referencePrice.gt(0)) return referencePrice;
+ if (activeAssetCtx?.ctx?.markPrice) {
+ const markPx = new BigNumber(activeAssetCtx.ctx.markPrice);
+ return markPx.isFinite() && markPx.gt(0) ? markPx : null;
+ }
+ return null;
+ };
+
+ // Get safe leverage value (fallback to 1x if invalid)
+ const getSafeLeverage = (): BigNumber => {
+ if (!leverage) return new BigNumber(1);
+ const leverageBN = new BigNumber(leverage);
+ return leverageBN.isFinite() && leverageBN.gt(0)
+ ? leverageBN
+ : new BigNumber(1);
+ };
+
+ const effectivePrice = getEffectivePrice();
+ const safeLeverage = getSafeLeverage();
+ const currentValue = new BigNumber(formData.size || 0);
+
+ // Calculate maximum trade size: Available Balance × Leverage ÷ Price
+ const calculateMaxSize = (): number => {
+ if (!effectivePrice || effectivePrice.lte(0)) return 0;
+ if (!Number.isFinite(availableToTradeValue) || availableToTradeValue <= 0)
+ return 0;
+
+ const maxTokens = new BigNumber(availableToTradeValue)
+ .multipliedBy(safeLeverage)
+ .dividedBy(effectivePrice)
+ .decimalPlaces(
+ activeAsset?.universe?.szDecimals ?? 2,
+ BigNumber.ROUND_FLOOR,
+ );
+
+ return maxTokens.isFinite() && maxTokens.gt(0) ? maxTokens.toNumber() : 0;
+ };
+
+ const maxSize = calculateMaxSize();
+ const currentValueNum = currentValue.isFinite()
+ ? currentValue.toNumber()
+ : 0;
+
+ return {
+ price: effectivePrice,
+ leverage: safeLeverage,
+ maxSize,
+ currentValue: currentValueNum,
+ controlledValue: maxSize > 0 ? Math.min(currentValueNum, maxSize) : 0,
+ isValid: !!effectivePrice && effectivePrice.gt(0) && maxSize > 0,
+ };
+ }, [
+ referencePrice,
+ activeAssetCtx?.ctx?.markPrice,
+ activeAsset?.universe?.szDecimals,
+ leverage,
+ availableToTradeValue,
+ formData.size,
+ ]);
+
+ const sliderStep = useMemo(() => {
+ const decimals = Math.min(activeAsset?.universe?.szDecimals ?? 2, 6);
+ return Number((10 ** -decimals).toFixed(decimals));
+ }, [activeAsset?.universe?.szDecimals]);
+
+ const handleSizeSliderChange = useCallback(
+ (value?: number) => {
+ if (value === undefined) return;
+ if (!sliderConfig.isValid) return;
+ const decimals = activeAsset?.universe?.szDecimals ?? 2;
+ const formatted = new BigNumber(value)
+ .decimalPlaces(decimals, BigNumber.ROUND_FLOOR)
+ .toFixed();
+ updateForm({ size: formatted });
+ },
+ [sliderConfig.isValid, activeAsset?.universe?.szDecimals, updateForm],
+ );
+
const handleTpslCheckboxChange = useCallback(
(checked: ICheckedState) => {
updateForm({ hasTpsl: !!checked });
@@ -231,7 +323,7 @@ function PerpTradingForm({
- ${availableToTrade}
+ ${availableToTradeDisplay}
@@ -297,6 +389,17 @@ function PerpTradingForm({
onChange={(value) => updateForm({ size: value })}
isMobile={isMobile}
/>
+
{activeAssetData ? (
- ${availableToTrade}
+ ${availableToTradeDisplay}
) : (
@@ -462,6 +565,18 @@ function PerpTradingForm({
value={formData.size}
onChange={(value) => updateForm({ size: value })}
/>
+