Skip to content

Commit

Permalink
new order: add estimated receive + tooltip to new order flow
Browse files Browse the repository at this point in the history
  • Loading branch information
sehyunc committed Jan 23, 2025
1 parent 6dd4a81 commit f1ed7b1
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 59 deletions.
10 changes: 5 additions & 5 deletions app/stats/charts/time-to-fill-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ export function TimeToFillCard() {
const [selectedTicker, setSelectedToken] = React.useState("WETH")
const [isSell, setIsSell] = React.useState(true)

const { priceInBase, priceInUsd } = useOrderValue({
const { valueInQuoteCurrency, valueInBaseCurrency } = useOrderValue({
amount: selectedAmount.toString(),
base: selectedTicker,
isQuoteCurrency: true,
isSell,
})
console.log("🚀 ~ TimeToFillCard ~ priceInBase:", priceInBase)
const [debouncedUsdValue] = useDebounceValue(priceInUsd, 500)

const [debouncedUsdValue] = useDebounceValue(valueInQuoteCurrency, 500)

const { data: timeToFillMs, isLoading } = useTimeToFill({
amount: debouncedUsdValue,
Expand Down Expand Up @@ -105,14 +105,14 @@ export function TimeToFillCard() {
: "pointer-events-auto opacity-100",
)}
>
{Number(priceInBase) ? (
{Number(valueInBaseCurrency) ? (
<NumberFlow
className="text-center font-serif text-2xl font-bold sm:text-right"
format={{
maximumFractionDigits: 2,
}}
prefix={`${isSell ? "Sell" : "Buy"} `}
value={Number(priceInBase)}
value={Number(valueInBaseCurrency)}
onClick={() => setIsSell((prev) => !prev)}
/>
) : (
Expand Down
20 changes: 10 additions & 10 deletions app/trade/[base]/components/new-order/new-order-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ export function NewOrderForm({
defaultValues,
})
const fees = usePredictedFees(form.watch())
const { priceInUsd, priceInBase } = useOrderValue(form.watch())
const formattedOrderValue = Number(priceInUsd)
? formatCurrencyFromString(priceInUsd)
const { valueInQuoteCurrency, valueInBaseCurrency } = useOrderValue(form.watch())
const formattedOrderValue = Number(valueInQuoteCurrency)
? formatCurrencyFromString(valueInQuoteCurrency)
: "--"

const { data: price } = usePriceQuery(Token.findByTicker(base).address)
Expand All @@ -111,12 +111,12 @@ export function NewOrderForm({
if (name === "isQuoteCurrency") {
setCurrency(value.isQuoteCurrency ? "quote" : "base")
if (value.isQuoteCurrency) {
if (Number(priceInUsd) > 0) {
form.setValue("amount", priceInUsd)
if (Number(valueInQuoteCurrency) > 0) {
form.setValue("amount", valueInQuoteCurrency)
}
} else {
if (Number(priceInBase) > 0) {
form.setValue("amount", priceInBase)
if (Number(valueInBaseCurrency) > 0) {
form.setValue("amount", valueInBaseCurrency)
}
}
}
Expand All @@ -125,7 +125,7 @@ export function NewOrderForm({
}
})
return () => subscription.unsubscribe()
}, [form, priceInBase, priceInUsd, setCurrency, setSide])
}, [form, valueInBaseCurrency, valueInQuoteCurrency, setCurrency, setSide])

React.useEffect(() => {
const unbind = orderFormEvents.on("reset", () => {
Expand All @@ -151,7 +151,7 @@ export function NewOrderForm({
})

function handleSubmit(values: z.infer<typeof formSchema>) {
if (parseFloat(priceInUsd) < 1) {
if (parseFloat(valueInQuoteCurrency) < 1) {
form.setError("amount", {
message: "Order value must be at least 1 USDC",
})
Expand All @@ -163,7 +163,7 @@ export function NewOrderForm({
onSubmit({
...values,
...fees,
amount: values.isQuoteCurrency ? priceInBase : values.amount,
amount: values.isQuoteCurrency ? valueInBaseCurrency : values.amount,
})
}
})
Expand Down
24 changes: 21 additions & 3 deletions components/dialogs/order-stepper/desktop/steps/default.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ import { usePrepareCreateOrder } from "@/hooks/use-prepare-create-order"
import { usePriceQuery } from "@/hooks/use-price-query"
import { Side } from "@/lib/constants/protocol"
import { constructStartToastMessage } from "@/lib/constants/task"
import { GAS_FEE_TOOLTIP } from "@/lib/constants/tooltips"
import { formatNumber, safeParseUnits } from "@/lib/format"
import { GAS_FEE_TOOLTIP, ESTIMATED_RECV_TOOLTIP } from "@/lib/constants/tooltips"
import { formatCurrencyFromString, formatNumber, safeParseUnits } from "@/lib/format"
import { decimalCorrectPrice } from "@/lib/utils"
import { useOrderValue } from "@/hooks/use-order-value"

export function DefaultStep(
props: NewOrderConfirmationProps & {
Expand Down Expand Up @@ -137,6 +138,13 @@ export function ConfirmOrderDisplay(
token.decimals,
true,
)
const { valueInQuoteCurrency } = useOrderValue({
...props,
isQuoteCurrency: false
})
const formattedValueInQuoteCurrency = Number(valueInQuoteCurrency)
? formatCurrencyFromString(valueInQuoteCurrency)
: "--"
return (
<>
<div className="space-y-3">
Expand All @@ -155,7 +163,17 @@ export function ConfirmOrderDisplay(
{props.isSell ? "For" : "With"}
</div>
<div className="flex items-center justify-between">
<div className="font-serif text-3xl font-bold">USDC</div>
<ResponsiveTooltip>
<ResponsiveTooltipTrigger
className="cursor-default"
onClick={(e) => isDesktop && e.preventDefault()}
>
<div className="font-serif text-3xl font-bold">~{formattedValueInQuoteCurrency} USDC</div>
</ResponsiveTooltipTrigger>
<ResponsiveTooltipContent>
{ESTIMATED_RECV_TOOLTIP}
</ResponsiveTooltipContent>
</ResponsiveTooltip>
<TokenIcon ticker="USDC" />
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions hooks/use-fees-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ export function useFeesCheck({
params: NewOrderConfirmationProps
}) {
let shouldDisplaySavings = false
const { priceInUsd } = useOrderValue({ ...params })
const { valueInQuoteCurrency } = useOrderValue({ ...params })

const threshold = parseFloat(priceInUsd) * savingsThreshold
const threshold = parseFloat(valueInQuoteCurrency) * savingsThreshold

if (params.predictedSavings > threshold) {
shouldDisplaySavings = true
Expand Down
56 changes: 40 additions & 16 deletions hooks/use-order-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ import { usePriceQuery } from "@/hooks/use-price-query"
import { amountTimesPrice } from "@/hooks/use-usd-price"
import { safeParseUnits } from "@/lib/format"

/**
* Custom hook to calculate the order value in both quote and base currency.
*
* @param {Object} params - The parameters for the hook.
* @param {string} params.amount - The amount of the base currency (decimal corrected).
* @param {string} params.base - The ticker symbol of the base currency.
* @param {boolean} params.isQuoteCurrency - Indicates if the amount is in quote currency.
* @returns {Object} - An object containing the value in quote currency and the value in base currency, both decimal corrected.
*/
export function useOrderValue({
amount,
base,
Expand All @@ -17,43 +26,58 @@ export function useOrderValue({
const baseToken = Token.findByTicker(base)
const quoteToken = Token.findByTicker("USDC")
const { data: usdPerBase } = usePriceQuery(baseToken.address)

// Calculate the inverse of the USD price per base token
const basePerUsd = React.useMemo(() => {
if (!usdPerBase) return ""
return 1 / usdPerBase
}, [usdPerBase])

const priceInUsd = React.useMemo(() => {
// Calculate the value in quote currency
const valueInQuoteCurrency = React.useMemo(() => {
if (!usdPerBase) return ""

// If the amount is in quote currency, return the amount directly
if (isQuoteCurrency) {
return amount
}
const parsedAmount = safeParseUnits(amount, baseToken.decimals)
if (parsedAmount instanceof Error) {

// Convert amount to non-decimal corrected amount
const rawAmount = safeParseUnits(amount, baseToken.decimals)
if (rawAmount instanceof Error) {
return ""
}
return formatUnits(
amountTimesPrice(parsedAmount, usdPerBase),
baseToken.decimals,
)

// Calculate the value in quote currency
const valueInQuote = amountTimesPrice(rawAmount, usdPerBase)
const decimalCorrectedValue = formatUnits(valueInQuote, baseToken.decimals)
return decimalCorrectedValue
}, [amount, baseToken.decimals, isQuoteCurrency, usdPerBase])

const priceInBase = React.useMemo(() => {
// Calculate the value in base currency
const valueInBaseCurrency = React.useMemo(() => {
if (!basePerUsd) return ""

// If the amount is in base currency, return the amount directly
if (!isQuoteCurrency) {
return amount
}
const parsedAmount = safeParseUnits(amount, quoteToken.decimals)
if (parsedAmount instanceof Error) {

// Convert amount to non-decimal corrected amount
const rawAmount = safeParseUnits(amount, quoteToken.decimals)
if (rawAmount instanceof Error) {
return ""
}
return formatUnits(
amountTimesPrice(parsedAmount, basePerUsd),
quoteToken.decimals,
)

// Calculate the value in base currency
const valueInBase = amountTimesPrice(rawAmount, basePerUsd)
const decimalCorrectedValue = formatUnits(valueInBase, quoteToken.decimals)
return decimalCorrectedValue
}, [amount, basePerUsd, isQuoteCurrency, quoteToken.decimals])

// TODO: Calculations should only be done with bigints
return {
priceInUsd,
priceInBase,
valueInQuoteCurrency,
valueInBaseCurrency,
}
}
35 changes: 12 additions & 23 deletions hooks/use-predicted-fees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,49 +6,38 @@ import { useOrderValue } from "@/hooks/use-order-value"
import { useSavings } from "@/hooks/use-savings-query"
import { PROTOCOL_FEE, RELAYER_FEE } from "@/lib/constants/protocol"

// TODO: Refactor to rely solely on useOrderValue
export function usePredictedFees({
amount,
base,
isSell,
isQuoteCurrency,
}: NewOrderFormProps) {
const { priceInBase, priceInUsd } = useOrderValue({
amount,
base,
isSell,
isQuoteCurrency,
})
export function usePredictedFees(order: NewOrderFormProps) {
const { valueInBaseCurrency, valueInQuoteCurrency } = useOrderValue(order)

const feesCalculation = React.useMemo(() => {
let res = {
relayerFee: 0,
protocolFee: 0,
}
if (!priceInUsd) return res
res.protocolFee = parseFloat(priceInUsd) * PROTOCOL_FEE
res.relayerFee = parseFloat(priceInUsd) * RELAYER_FEE
if (!valueInQuoteCurrency) return res
res.protocolFee = parseFloat(valueInQuoteCurrency) * PROTOCOL_FEE
res.relayerFee = parseFloat(valueInQuoteCurrency) * RELAYER_FEE
return res
}, [priceInUsd])
}, [valueInQuoteCurrency])

// Amount should always be base amount (even if denominated in USDC)
const { data, isSuccess } = useSavings({
amount: priceInBase,
base,
isSell,
isQuoteCurrency,
amount: valueInBaseCurrency,
base: order.base,
isSell: order.isSell,
isQuoteCurrency: order.isQuoteCurrency,
})
const [predictedSavings, setPredictedSavings] = React.useState(0)
React.useEffect(() => {
setPredictedSavings((prev) => {
if (isSuccess && prev !== data.savings) {
return data.savings
} else if (!amount) {
} else if (!order.amount) {
return 0
}
return prev
})
}, [amount, data?.savings, isSuccess])
}, [order.amount, data?.savings, isSuccess])

return {
...feesCalculation,
Expand Down
2 changes: 2 additions & 0 deletions lib/constants/tooltips.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@ export const TRANSFER_DIALOG_L1_BALANCE_TOOLTIP =
"Bridge to Arbitrum to deposit."
export const ORDER_FORM_DEPOSIT_WARNING = ({ ticker }: { ticker: string }) =>
`Deposit ${ticker} or USDC to your wallet to place an order.`
export const ESTIMATED_RECV_TOOLTIP =
"This is an estimate based on the current Binance price. This may change depending on when a matching order is found.";

0 comments on commit f1ed7b1

Please sign in to comment.