diff --git a/packages/server/src/queries/complex/orderbooks/denoms.ts b/packages/server/src/queries/complex/orderbooks/denoms.ts new file mode 100644 index 0000000000..12c6d5595c --- /dev/null +++ b/packages/server/src/queries/complex/orderbooks/denoms.ts @@ -0,0 +1,28 @@ +import { Chain } from "@osmosis-labs/types"; +import cachified, { CacheEntry } from "cachified"; +import { LRUCache } from "lru-cache"; + +import { DEFAULT_LRU_OPTIONS } from "../../../utils/cache"; +import { queryOrderbookDenoms } from "../../osmosis"; + +const orderbookDenomsCache = new LRUCache( + DEFAULT_LRU_OPTIONS +); + +export function getOrderbookDenoms({ + orderbookAddress, + chainList, +}: { + orderbookAddress: string; + chainList: Chain[]; +}) { + return cachified({ + cache: orderbookDenomsCache, + key: `orderbookDenoms-${orderbookAddress}`, + ttl: 1000 * 60 * 60 * 24 * 7, // 7 days + getFreshValue: () => + queryOrderbookDenoms({ orderbookAddress, chainList }).then( + ({ data }) => data + ), + }); +} diff --git a/packages/server/src/queries/complex/orderbooks/index.ts b/packages/server/src/queries/complex/orderbooks/index.ts index 298323479c..773bec1850 100644 --- a/packages/server/src/queries/complex/orderbooks/index.ts +++ b/packages/server/src/queries/complex/orderbooks/index.ts @@ -1,4 +1,5 @@ export * from "./active-orders"; +export * from "./denoms"; export * from "./maker-fee"; export * from "./spot-price"; export * from "./tick-state"; diff --git a/packages/server/src/queries/complex/orderbooks/tick-state.ts b/packages/server/src/queries/complex/orderbooks/tick-state.ts index ffedaafb42..c7637d6ac0 100644 --- a/packages/server/src/queries/complex/orderbooks/tick-state.ts +++ b/packages/server/src/queries/complex/orderbooks/tick-state.ts @@ -24,7 +24,7 @@ export function getOrderbookTickState({ key: `orderbookTickInfo-${orderbookAddress}-${tickIds .sort((a, b) => a - b) .join(",")}`, - ttl: 1000 * 60 * 2, // 2 minutes + ttl: 1000 * 30, // 30 seconds getFreshValue: () => queryOrderbookTicks({ orderbookAddress, chainList, tickIds }).then( ({ data }) => data.ticks @@ -46,7 +46,7 @@ export function getOrderbookTickUnrealizedCancels({ key: `orderbookTickUnrealizedCancels-${orderbookAddress}-${tickIds .sort((a, b) => a - b) .join(",")}`, - ttl: 1000 * 60 * 2, // 2 minutes + ttl: 1000 * 30, // 30 seconds getFreshValue: () => queryOrderbookTickUnrealizedCancelsById({ orderbookAddress, diff --git a/packages/server/src/queries/osmosis/orderbooks.ts b/packages/server/src/queries/osmosis/orderbooks.ts index 17301dc70c..197ecee4c5 100644 --- a/packages/server/src/queries/osmosis/orderbooks.ts +++ b/packages/server/src/queries/osmosis/orderbooks.ts @@ -145,3 +145,25 @@ export const queryOrderbookSpotPrice = createNodeQuery< return `/cosmwasm/wasm/v1/contract/${orderbookAddress}/smart/${encodedMsg}`; }, }); + +interface OrderbookDenomsResponse { + data: { + quote_denom: string; + base_denom: string; + }; +} + +export const queryOrderbookDenoms = createNodeQuery< + OrderbookDenomsResponse, + { + orderbookAddress: string; + } +>({ + path: ({ orderbookAddress }) => { + const msg = JSON.stringify({ + denoms: {}, + }); + const encodedMsg = Buffer.from(msg).toString("base64"); + return `/cosmwasm/wasm/v1/contract/${orderbookAddress}/smart/${encodedMsg}`; + }, +}); diff --git a/packages/trpc/src/orderbook-router.ts b/packages/trpc/src/orderbook-router.ts index 5ee23adde4..5345699fc8 100644 --- a/packages/trpc/src/orderbook-router.ts +++ b/packages/trpc/src/orderbook-router.ts @@ -3,6 +3,7 @@ import { tickToPrice } from "@osmosis-labs/math"; import { CursorPaginationSchema, getOrderbookActiveOrders, + getOrderbookDenoms, getOrderbookMakerFee, getOrderbookSpotPrice, getOrderbookTickState, @@ -11,6 +12,7 @@ import { maybeCachePaginatedItems, } from "@osmosis-labs/server"; import { Chain } from "@osmosis-labs/types"; +import { getAssetFromAssetList } from "@osmosis-labs/utils"; import { z } from "zod"; import { createTRPCRouter, publicProcedure } from "./api"; @@ -41,6 +43,9 @@ export type MappedLimitOrder = Omit< orderbookAddress: string; price: Dec; status: OrderStatus; + output: number; + quoteAsset: ReturnType; + baseAsset: ReturnType; }; function mapOrderStatus(order: LimitOrder): OrderStatus { @@ -56,7 +61,9 @@ function mapOrderStatus(order: LimitOrder): OrderStatus { async function getTickInfoAndTransformOrders( orderbookAddress: string, orders: LimitOrder[], - chainList: Chain[] + chainList: Chain[], + quoteAsset: ReturnType, + baseAsset: ReturnType ): Promise { const tickIds = [...new Set(orders.map((o) => o.tick_id))]; const tickStates = await getOrderbookTickState({ @@ -81,11 +88,20 @@ async function getTickInfoAndTransformOrders( const { tickState, unrealizedCancels } = fullTickState.find( ({ tickId }) => tickId === o.tick_id ) ?? { tickState: undefined, unrealizedCancels: undefined }; - const quantity = parseInt(o.quantity); - const placedQuantity = parseInt(o.placed_quantity); + + const [tokenInAsset, tokenOutAsset] = + o.order_direction === "bid" + ? [quoteAsset, baseAsset] + : [baseAsset, quoteAsset]; + + const quantityMin = parseInt(o.quantity); + const placedQuantityMin = parseInt(o.placed_quantity); + const placedQuantity = + placedQuantityMin / 10 ** (tokenInAsset?.decimals ?? 0); + const quantity = quantityMin / 10 ** (tokenInAsset?.decimals ?? 0); const percentClaimed = new Dec( - (placedQuantity - quantity) / placedQuantity + (placedQuantityMin - quantityMin) / placedQuantityMin ); const [tickEtas, tickCumulativeCancelled, tickUnrealizedCancelled] = o.order_direction === "bid" @@ -116,12 +132,23 @@ async function getTickInfoAndTransformOrders( const tickTotalEtas = tickEtas + (tickUnrealizedCancelled - tickCumulativeCancelled); const totalFilled = Math.max( - tickTotalEtas - (parseInt(o.etas) - (placedQuantity - quantity)), + tickTotalEtas - (parseInt(o.etas) - (placedQuantityMin - quantityMin)), 0 ); - const percentFilled = new Dec(totalFilled / placedQuantity); + const percentFilled = new Dec(totalFilled / placedQuantityMin); const price = tickToPrice(new Int(o.tick_id)); const status = mapOrderStatus(o); + + const outputMin = + o.order_direction === "bid" + ? new Dec(placedQuantityMin).quo(price) + : new Dec(placedQuantityMin).mul(price); + const output = parseInt( + outputMin + .quo(new Dec(10 ** (tokenOutAsset?.decimals ?? 0))) + .truncate() + .toString() + ); return { ...o, price, @@ -132,6 +159,9 @@ async function getTickInfoAndTransformOrders( percentFilled, orderbookAddress, status, + output, + quoteAsset, + baseAsset, }; }) .sort((a, b) => a.order_id - b.order_id); @@ -169,11 +199,24 @@ export const orderbookRouter = createTRPCRouter({ }); if (resp.orders.length === 0) return []; - + const { quote_denom, base_denom } = await getOrderbookDenoms({ + orderbookAddress: contractOsmoAddress, + chainList: ctx.chainList, + }); + const quoteAsset = getAssetFromAssetList({ + assetLists: ctx.assetLists, + sourceDenom: quote_denom, + }); + const baseAsset = getAssetFromAssetList({ + assetLists: ctx.assetLists, + sourceDenom: base_denom, + }); return getTickInfoAndTransformOrders( contractOsmoAddress, resp.orders, - ctx.chainList + ctx.chainList, + quoteAsset, + baseAsset ); }, cacheKey: JSON.stringify([ @@ -195,8 +238,9 @@ export const orderbookRouter = createTRPCRouter({ const { contractAddresses, userOsmoAddress } = input; if (contractAddresses.length === 0 || userOsmoAddress.length === 0) return []; + const promises = contractAddresses.map( - async (contractOsmoAddress) => { + async (contractOsmoAddress: string) => { const resp = await getOrderbookActiveOrders({ orderbookAddress: contractOsmoAddress, userOsmoAddress: userOsmoAddress, @@ -204,11 +248,25 @@ export const orderbookRouter = createTRPCRouter({ }); if (resp.orders.length === 0) return []; - + const { base_denom } = await getOrderbookDenoms({ + orderbookAddress: contractOsmoAddress, + chainList: ctx.chainList, + }); + // TODO: Use actual quote denom here + const quoteAsset = getAssetFromAssetList({ + assetLists: ctx.assetLists, + sourceDenom: "uusdc", + }); + const baseAsset = getAssetFromAssetList({ + assetLists: ctx.assetLists, + sourceDenom: base_denom, + }); const mappedOrders = await getTickInfoAndTransformOrders( contractOsmoAddress, resp.orders, - ctx.chainList + ctx.chainList, + quoteAsset, + baseAsset ); return mappedOrders; } diff --git a/packages/web/components/complex/orders-history/cells/filled-progress.tsx b/packages/web/components/complex/orders-history/cells/filled-progress.tsx index 73f65d95fd..e7c0f74d03 100644 --- a/packages/web/components/complex/orders-history/cells/filled-progress.tsx +++ b/packages/web/components/complex/orders-history/cells/filled-progress.tsx @@ -15,7 +15,7 @@ export const OrderProgressBar: React.FC = ({ const { percentFilled, status } = order; const roundedAmountFilled = useMemo(() => { - if (percentFilled.lt(new Dec(1))) { + if (percentFilled.lt(new Dec(1)) && !percentFilled.isZero()) { return new Dec(1); } return percentFilled.round(); diff --git a/packages/web/components/complex/orders-history/columns.tsx b/packages/web/components/complex/orders-history/columns.tsx index 36f1c46bff..95e28dab78 100644 --- a/packages/web/components/complex/orders-history/columns.tsx +++ b/packages/web/components/complex/orders-history/columns.tsx @@ -1,3 +1,5 @@ +import { PricePretty } from "@keplr-wallet/unit"; +import { DEFAULT_VS_CURRENCY } from "@osmosis-labs/server"; import { createColumnHelper } from "@tanstack/react-table"; import classNames from "classnames"; import Image from "next/image"; @@ -6,6 +8,7 @@ import { Icon } from "~/components/assets"; import { ActionsCell } from "~/components/complex/orders-history/cells/actions"; import { OrderProgressBar } from "~/components/complex/orders-history/cells/filled-progress"; import { DisplayableLimitOrder } from "~/hooks/limit-orders/use-orderbook"; +import { formatPretty } from "~/utils/formatter"; const columnHelper = createColumnHelper(); @@ -22,7 +25,8 @@ export const tableColumns = [ order_direction, quoteAsset, baseAsset, - // placed_quantity, + placed_quantity, + output, // quantity, // tick_id, // percentFilled, @@ -35,7 +39,6 @@ export const tableColumns = [ baseAsset?.rawAsset.logoURIs.svg ?? baseAsset?.rawAsset.logoURIs.png ?? ""; - return (
@@ -56,18 +59,31 @@ export const tableColumns = [ { "flex-row-reverse": order_direction === "bid" } )} > - 1000 {baseAsset?.symbol} + + {order_direction === "bid" ? output : placed_quantity}{" "} + {baseAsset?.symbol} + - 0.123 {quoteAsset?.symbol} + + {order_direction === "bid" ? placed_quantity : output}{" "} + {quoteAsset?.symbol} +

- {order_direction === "bid" ? "Buy" : "Sell"} $1000 of + {order_direction === "bid" ? "Buy" : "Sell"}{" "} + {formatPretty( + new PricePretty( + DEFAULT_VS_CURRENCY, + order_direction === "bid" ? placed_quantity : output + ) + )}{" "} + of { return ( @@ -98,7 +114,7 @@ export const tableColumns = [

{baseAsset?.symbol} ยท Limit

-

$67,890.10

+

{formatPretty(new PricePretty(DEFAULT_VS_CURRENCY, price))}

); }, diff --git a/packages/web/hooks/limit-orders/use-orderbook.ts b/packages/web/hooks/limit-orders/use-orderbook.ts index 98afe442b4..b10e1213d9 100644 --- a/packages/web/hooks/limit-orders/use-orderbook.ts +++ b/packages/web/hooks/limit-orders/use-orderbook.ts @@ -267,10 +267,7 @@ export const useActiveLimitOrdersByOrderbook = ({ }; }; -export type DisplayableLimitOrder = MappedLimitOrder & { - baseAsset: ReturnType; - quoteAsset: ReturnType; -}; +export type DisplayableLimitOrder = MappedLimitOrder; export const useOrderbookAllActiveOrders = ({ userAddress, @@ -298,27 +295,8 @@ export const useOrderbookAllActiveOrders = ({ const allOrders = useMemo(() => { return orders?.pages.flatMap((page) => page.items) ?? []; }, [orders]); - const ordersWithDenoms: DisplayableLimitOrder[] = useMemo(() => { - return allOrders.map((o) => { - const orderbook = orderbooks.find( - (ob) => ob.contractAddress === o.orderbookAddress - ); - return { - ...o, - baseAsset: getAssetFromAssetList({ - coinMinimalDenom: orderbook?.baseDenom ?? "", - assetLists: AssetLists, - }), - quoteAsset: getAssetFromAssetList({ - coinMinimalDenom: orderbook?.quoteDenom ?? "", - assetLists: AssetLists, - }), - }; - }); - }, [allOrders, orderbooks]); - return { - orders: ordersWithDenoms, + orders: allOrders, isLoading, fetchNextPage, isFetching,