Skip to content

Commit

Permalink
Merge pull request #3673 from osmosis-labs/connor/price-selection-def…
Browse files Browse the repository at this point in the history
…ault

[Limit Price - V3]: Price Input Default Values
  • Loading branch information
crnbarr93 authored Aug 5, 2024
2 parents 1a9fd45 + 0b97b5a commit 1e1828e
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 53 deletions.
121 changes: 79 additions & 42 deletions packages/web/components/place-limit-tool/limit-price-selector.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Dec } from "@keplr-wallet/unit";
import classNames from "classnames";
import { parseAsString, useQueryState } from "nuqs";
import React, { FC, useCallback, useEffect, useMemo, useState } from "react";
import AutosizeInput from "react-input-autosize";
import { useMeasure } from "react-use";
Expand Down Expand Up @@ -33,10 +34,11 @@ export const LimitPriceSelector: FC<LimitPriceSelectorProps> = ({
swapState,
orderDirection,
}) => {
const [tab] = useQueryState("tab", parseAsString.withDefault("swap"));
const [input, setInput] = useState<HTMLInputElement | null>(null);
const { t } = useTranslation();
const { priceState } = swapState;
const [inputMode, setInputMode] = useState(InputMode.Percentage);
const [inputMode, setInputMode] = useState(InputMode.Price);

const swapInputMode = useCallback(() => {
setInputMode(
Expand All @@ -45,32 +47,47 @@ export const LimitPriceSelector: FC<LimitPriceSelectorProps> = ({
: InputMode.Percentage
);

if (inputMode === InputMode.Price) {
priceState.setPriceLock(false);
} else {
priceState.setPriceLock(true);
}

if (priceState.isBeyondOppositePrice || !priceState.manualPercentAdjusted) {
priceState.setPercentAdjusted("0");
}

if (input) input.focus();
}, [inputMode, input]);
}, [inputMode, input, priceState]);

// Adjust the percentage adjusted when the spot price changes and user is inputting a price
// Adjust order price as spot price changes until user inputs a price
useEffect(() => {
if (
priceState.spotPrice &&
priceState.orderPrice.length > 0 &&
inputMode === InputMode.Price
) {
const manualPrice = new Dec(priceState.orderPrice);
const percentAdjusted = manualPrice
.quo(priceState.spotPrice)
.sub(new Dec(1))
.mul(new Dec(100));
if (inputMode === InputMode.Price && !priceState.priceLocked) {
const formattedPrice = formatPretty(
priceState.spotPrice,
getPriceExtendedFormatOptions(priceState.spotPrice)
).replace(/,/g, "");
priceState._setPriceUnsafe(formattedPrice);
priceState._setPercentAdjustedUnsafe("0");
}

priceState._setPercentAdjustedUnsafe(
percentAdjusted.isZero() || manualPrice.isZero()
? ""
: formatPretty(percentAdjusted.abs(), {
maxDecimals: 3,
}).toString()
if (inputMode === InputMode.Percentage) {
priceState.setPriceAsPercentageOfSpotPrice(
new Dec(
!!priceState.manualPercentAdjusted
? priceState.manualPercentAdjusted.replace(/,/g, "")
: 0
).quo(new Dec(100)),
false
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [priceState.spotPrice, priceState.orderPrice, inputMode]);
}, [
inputMode,
priceState.priceFiat,
priceState.priceLocked,
priceState.spotPrice,
]);

const priceLabel = useMemo(() => {
if (inputMode === InputMode.Percentage) {
Expand Down Expand Up @@ -104,6 +121,13 @@ export const LimitPriceSelector: FC<LimitPriceSelectorProps> = ({

const [containerRef, { width }] = useMeasure<HTMLDivElement>();

const isPercentTooLarge = useMemo(() => {
if (tab !== "buy") return false;

const maxPercentage = new Dec(99.999);
return priceState.percentAdjusted.abs().gt(maxPercentage);
}, [priceState.percentAdjusted, tab]);

return (
<div
ref={containerRef}
Expand Down Expand Up @@ -198,24 +222,31 @@ export const LimitPriceSelector: FC<LimitPriceSelectorProps> = ({
}}
/>
) : (
<AutosizeInput
type="text"
extraWidth={0}
inputClassName="bg-transparent text-white-full transition-colors placeholder:text-osmoverse-600"
value={swapState.priceState.manualPercentAdjusted}
placeholder={trimPlaceholderZeros(
swapState.priceState.percentAdjusted
.mul(new Dec(100))
.abs()
.toString()
)}
inputRef={setInput}
onChange={(e) =>
swapState.priceState.setPercentAdjusted(
e.target.value.replace("%", "")
)
}
/>
<>
{isPercentTooLarge && <span>{">"}</span>}
<AutosizeInput
type="text"
extraWidth={0}
inputClassName="bg-transparent text-white-full transition-colors placeholder:text-osmoverse-600"
value={
isPercentTooLarge
? "99.999"
: swapState.priceState.manualPercentAdjusted
}
placeholder={trimPlaceholderZeros(
swapState.priceState.percentAdjusted
.mul(new Dec(100))
.abs()
.toString()
)}
inputRef={setInput}
onChange={(e) =>
swapState.priceState.setPercentAdjusted(
e.target.value.replace("%", "")
)
}
/>
</>
)}
{inputMode === InputMode.Percentage && (
<span className="inline-flex items-baseline gap-1">
Expand All @@ -242,10 +273,16 @@ export const LimitPriceSelector: FC<LimitPriceSelectorProps> = ({
className="flex h-8 w-full items-center justify-center rounded-5xl border border-[#6B62AD] px-3 py-1 text-wosmongton-200 transition hover:border-transparent hover:bg-osmoverse-alpha-800/[.54] hover:text-white-high disabled:opacity-50"
key={`limit-price-adjust-${label}`}
onClick={() => {
priceState.setPrice("");
priceState.setPercentAdjusted(
formatPretty(value.mul(new Dec(100)))
);
if (inputMode === InputMode.Percentage) {
priceState.setPercentAdjusted(
formatPretty(value.mul(new Dec(100)))
);
} else {
priceState.setPriceAsPercentageOfSpotPrice(value);
priceState._setPercentAdjustedUnsafe(
formatPretty(value.mul(new Dec(100)))
);
}
}}
disabled={!priceState.spotPrice || priceState.isLoading}
>
Expand Down
88 changes: 77 additions & 11 deletions packages/web/hooks/limit-orders/use-place-limit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useAmplitudeAnalytics } from "~/hooks/use-amplitude-analytics";
import { useEstimateTxFees } from "~/hooks/use-estimate-tx-fees";
import { useSwap, useSwapAssets } from "~/hooks/use-swap";
import { useStore } from "~/stores";
import { formatPretty, getPriceExtendedFormatOptions } from "~/utils/formatter";
import { countDecimals, trimPlaceholderZeros } from "~/utils/number";
import { api } from "~/utils/trpc";

Expand Down Expand Up @@ -615,14 +616,41 @@ const useLimitPrice = ({
{ refetchInterval: 5000, enabled: !!baseDenom && !priceLocked }
);

const [orderPrice, setOrderPrice] = useState("");
const [manualPercentAdjusted, setManualPercentAdjusted] = useState("");
const [orderPrice, setOrderPrice] = useState("0");
const [manualPercentAdjusted, setManualPercentAdjusted] = useState("0");

// Decimal version of the spot price, defaults to 1
const spotPrice = useMemo(() => {
return assetPrice ? assetPrice.toDec() : new Dec(1);
return assetPrice
? new Dec(
formatPretty(
assetPrice.toDec(),
getPriceExtendedFormatOptions(assetPrice.toDec())
).replace(/,/g, "")
)
: new Dec(1);
}, [assetPrice]);

const setPriceAsPercentageOfSpotPrice = useCallback(
(percent: Dec, lockPrice = true) => {
const percentAdjusted =
orderDirection === "bid"
? // Adjust negatively for bid orders
new Dec(1).sub(percent)
: // Adjust positively for ask orders
new Dec(1).add(percent);
const newPrice = spotPrice.mul(percentAdjusted);
setOrderPrice(
formatPretty(newPrice, getPriceExtendedFormatOptions(newPrice)).replace(
/,/g,
""
)
);
setPriceLock(lockPrice);
},
[setOrderPrice, orderDirection, spotPrice]
);

// Sets a user based order price, if nothing is input it resets the form (including percentage adjustments)
const setManualOrderPrice = useCallback(
(price: string) => {
Expand All @@ -640,13 +668,37 @@ const useLimitPrice = ({
price = trimPlaceholderZeros(new Dec(MAX_TICK_PRICE).toString());
}

const percentAdjusted = newPrice
.quo(spotPrice)
.sub(new Dec(1))
.mul(new Dec(100));

const isPercentAdjustedTooLarge =
percentAdjusted.toString().split(".")[0].length > 9;

if (isPercentAdjustedTooLarge) return;

setPriceLock(true);
setOrderPrice(price);

if (price.length === 0 || newPrice.isZero()) {
setManualPercentAdjusted("");
return;
}

setManualPercentAdjusted(
percentAdjusted.isZero() || newPrice.isZero()
? "0"
: trimPlaceholderZeros(
formatPretty(percentAdjusted.abs(), {
maxDecimals: 3,
})
.toString()
.replace(/,/g, "")
)
);
},
[setOrderPrice]
[setOrderPrice, spotPrice]
);

// Adjusts the percentage for placing the order.
Expand Down Expand Up @@ -685,10 +737,17 @@ const useLimitPrice = ({

setManualPercentAdjusted(percentAdjusted);

// Reset the user's manual order price if they adjust percentage
if (orderPrice.length > 0) setOrderPrice("");
if (!percentAdjusted) {
setPriceAsPercentageOfSpotPrice(new Dec(0), false);
return;
}

setPriceAsPercentageOfSpotPrice(
new Dec(percentAdjusted).quo(new Dec(100)),
false
);
},
[setManualPercentAdjusted, orderPrice.length, orderDirection]
[setManualPercentAdjusted, orderDirection, setPriceAsPercentageOfSpotPrice]
);

// Whether the user's manual order price is a valid price
Expand Down Expand Up @@ -732,8 +791,11 @@ const useLimitPrice = ({

// The raw percentage adjusted based on the current order price state
const percentAdjusted = useMemo(
() => price.quo(spotPrice).sub(new Dec(1)),
[price, spotPrice]
() =>
!!manualPercentAdjusted
? new Dec(manualPercentAdjusted).quo(new Dec(100))
: price.quo(spotPrice).sub(new Dec(1)),
[price, spotPrice, manualPercentAdjusted]
);

// If the user is inputting a price that crosses over the spot price
Expand All @@ -746,13 +808,14 @@ const useLimitPrice = ({
}, [price]);

const reset = useCallback(() => {
setManualPercentAdjusted("");
setManualPercentAdjusted("0");
setOrderPrice("");
setPriceLock(false);
}, []);

useEffect(() => {
reset();
}, [orderDirection, reset]);
}, [orderDirection, reset, baseDenom]);

const isValidPrice = useMemo(() => {
return isValidInputPrice || Boolean(spotPrice);
Expand All @@ -770,9 +833,12 @@ const useLimitPrice = ({
isLoading: loadingSpotPrice,
reset,
setPrice: setManualOrderPrice,
_setPriceUnsafe: setOrderPrice,
isValidPrice,
isBeyondOppositePrice,
isSpotPriceRefetching,
setPriceLock,
priceLocked,
setPriceAsPercentageOfSpotPrice,
};
};

0 comments on commit 1e1828e

Please sign in to comment.