Skip to content

Commit

Permalink
Merge pull request #3612 from osmosis-labs/fabryscript/feat-add-v3-de…
Browse files Browse the repository at this point in the history
…signs

[Limit Orders]: Feat add v3 designs
  • Loading branch information
crnbarr93 authored Aug 1, 2024
2 parents eba4a19 + 22f3792 commit e3d40f4
Show file tree
Hide file tree
Showing 62 changed files with 4,192 additions and 2,268 deletions.
131 changes: 128 additions & 3 deletions packages/server/src/queries/complex/orderbooks/active-orders.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,157 @@
import { Chain } from "@osmosis-labs/types";
import { Dec, Int } from "@keplr-wallet/unit";
import { tickToPrice } from "@osmosis-labs/math";
import { AssetList, Chain } from "@osmosis-labs/types";
import { getAssetFromAssetList } from "@osmosis-labs/utils";
import cachified, { CacheEntry } from "cachified";
import dayjs from "dayjs";
import { LRUCache } from "lru-cache";

import { DEFAULT_LRU_OPTIONS } from "../../../utils/cache";
import { LimitOrder, queryOrderbookActiveOrders } from "../../osmosis";
import { getOrderbookDenoms } from "./denoms";
import {
getOrderbookTickState,
getOrderbookTickUnrealizedCancels,
} from "./tick-state";
import type { MappedLimitOrder, OrderStatus } from "./types";

const activeOrdersCache = new LRUCache<string, CacheEntry>(DEFAULT_LRU_OPTIONS);

export function getOrderbookActiveOrders({
orderbookAddress,
userOsmoAddress,
chainList,
assetLists,
}: {
orderbookAddress: string;
userOsmoAddress: string;
chainList: Chain[];
assetLists: AssetList[];
}) {
return cachified({
cache: activeOrdersCache,
key: `orderbookActiveOrders-${orderbookAddress}-${userOsmoAddress}`,
ttl: 1000 * 3, // 3 seconds
ttl: 500, // 2 seconds
getFreshValue: () =>
queryOrderbookActiveOrders({
orderbookAddress,
userAddress: userOsmoAddress,
chainList,
}).then(
({ data }: { data: { count: number; orders: LimitOrder[] } }) => data
async ({ data }: { data: { count: number; orders: LimitOrder[] } }) => {
const { quoteAsset, baseAsset } = await getOrderbookDenoms({
orderbookAddress,
chainList,
assetLists,
});

return getTickInfoAndTransformOrders(
orderbookAddress,
data.orders,
chainList,
quoteAsset,
baseAsset
);
}
),
});
}

function mapOrderStatus(order: LimitOrder, percentFilled: Dec): OrderStatus {
const quantInt = parseInt(order.quantity);
if (quantInt === 0 || percentFilled.equals(new Dec(1))) return "filled";
if (percentFilled.isZero()) return "open";
if (percentFilled.lt(new Dec(1))) return "partiallyFilled";

return "open";
}

async function getTickInfoAndTransformOrders(
orderbookAddress: string,
orders: LimitOrder[],
chainList: Chain[],
quoteAsset: ReturnType<typeof getAssetFromAssetList>,
baseAsset: ReturnType<typeof getAssetFromAssetList>
): Promise<MappedLimitOrder[]> {
const tickIds = [...new Set(orders.map((o) => o.tick_id))];
const tickStates = await getOrderbookTickState({
orderbookAddress,
chainList,
tickIds,
});
const unrealizedTickCancels = await getOrderbookTickUnrealizedCancels({
orderbookAddress,
chainList,
tickIds,
});

const fullTickState = tickStates.map(({ tick_id, tick_state }) => ({
tickId: tick_id,
tickState: tick_state,
unrealizedCancels: unrealizedTickCancels.find((c) => c.tick_id === tick_id),
}));

return orders.map((o) => {
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 percentClaimed = new Dec(
(placedQuantity - quantity) / placedQuantity
);

const normalizationFactor = new Dec(10).pow(
new Int((quoteAsset?.decimals ?? 0) - (baseAsset?.decimals ?? 0))
);
const [tickEtas, tickUnrealizedCancelled] =
o.order_direction === "bid"
? [
parseInt(
tickState?.bid_values.effective_total_amount_swapped ?? "0"
),
parseInt(
unrealizedCancels?.unrealized_cancels.bid_unrealized_cancels ??
"0"
),
]
: [
parseInt(
tickState?.ask_values.effective_total_amount_swapped ?? "0"
),
parseInt(
unrealizedCancels?.unrealized_cancels.ask_unrealized_cancels ??
"0"
),
];
const tickTotalEtas = tickEtas + tickUnrealizedCancelled;
const totalFilled = Math.max(
tickTotalEtas - (parseInt(o.etas) - (placedQuantity - quantity)),
0
);
const percentFilled = new Dec(Math.min(totalFilled / placedQuantity, 1));
const price = tickToPrice(new Int(o.tick_id));
const status = mapOrderStatus(o, percentFilled);
const output =
o.order_direction === "bid"
? new Dec(placedQuantity).quo(price)
: new Dec(placedQuantity).mul(price);
return {
...o,
price: price.quo(normalizationFactor),
quantity,
placed_quantity: placedQuantity,
percentClaimed,
totalFilled,
percentFilled,
orderbookAddress,
status,
output,
quoteAsset,
baseAsset,
placed_at: dayjs(parseInt(o.placed_at) / 1_000).unix(),
};
});
}
20 changes: 17 additions & 3 deletions packages/server/src/queries/complex/orderbooks/denoms.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Chain } from "@osmosis-labs/types";
import { AssetList, Chain } from "@osmosis-labs/types";
import { getAssetFromAssetList } from "@osmosis-labs/utils";
import cachified, { CacheEntry } from "cachified";
import { LRUCache } from "lru-cache";

Expand All @@ -12,17 +13,30 @@ const orderbookDenomsCache = new LRUCache<string, CacheEntry>(
export function getOrderbookDenoms({
orderbookAddress,
chainList,
assetLists,
}: {
orderbookAddress: string;
chainList: Chain[];
assetLists: AssetList[];
}) {
return cachified({
cache: orderbookDenomsCache,
key: `orderbookDenoms-${orderbookAddress}`,
ttl: 1000 * 60 * 60 * 24 * 7, // 7 days
ttl: 1000 * 60 * 60 * 24 * 30, // 30 days
getFreshValue: () =>
queryOrderbookDenoms({ orderbookAddress, chainList }).then(
({ data }) => data
({ data: { quote_denom, base_denom } }) => {
const quoteAsset = getAssetFromAssetList({
coinMinimalDenom: quote_denom,
assetLists,
});
const baseAsset = getAssetFromAssetList({
coinMinimalDenom: base_denom,
assetLists,
});

return { quoteAsset, baseAsset };
}
),
});
}
129 changes: 126 additions & 3 deletions packages/server/src/queries/complex/orderbooks/historical-orders.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
import { Dec, Int } from "@keplr-wallet/unit";
import { tickToPrice } from "@osmosis-labs/math";
import { AssetList, Chain } from "@osmosis-labs/types";
import { getAssetFromAssetList } from "@osmosis-labs/utils";
import cachified, { CacheEntry } from "cachified";
import dayjs from "dayjs";
import { LRUCache } from "lru-cache";

import { DEFAULT_LRU_OPTIONS } from "../../../utils/cache";
import { HistoricalLimitOrder, queryHistoricalOrders } from "../../osmosis";
import { getOrderbookDenoms } from "./denoms";
import type { MappedLimitOrder, OrderStatus } from "./types";

const orderbookHistoricalOrdersCache = new LRUCache<string, CacheEntry>(
DEFAULT_LRU_OPTIONS
);

export function getOrderbookHistoricalOrders({
userOsmoAddress,
assetLists,
chainList,
}: {
userOsmoAddress: string;
assetLists: AssetList[];
chainList: Chain[];
}) {
return cachified({
cache: orderbookHistoricalOrdersCache,
key: `orderbookHistoricalOrders-${userOsmoAddress}`,
ttl: 1000 * 3, // 3 seconds
ttl: 1000 * 2, // 2 seconds
getFreshValue: () =>
queryHistoricalOrders(userOsmoAddress).then((data) => {
queryHistoricalOrders(userOsmoAddress).then(async (data) => {
const orders = data;
orders.forEach((o) => {
if (o.status === "cancelled" && o.claimed_quantity !== "0") {
Expand All @@ -30,7 +41,119 @@ export function getOrderbookHistoricalOrders({
orders.push(newOrder);
}
});
return orders;

return await mapHistoricalToMapped(
orders,
userOsmoAddress,
assetLists,
chainList
);
}),
});
}

/**
* Gets an object containing a mapping between an orderbook address and it's quote and base asset.
* Each orderbook address is fetched once and only those present in the provided orders are queried.
*/
async function getRelevantOrderbookDenoms(
historicalOrders: HistoricalLimitOrder[],
assetLists: AssetList[],
chainList: Chain[]
): Promise<
Record<
string,
{
quoteAsset: ReturnType<typeof getAssetFromAssetList>;
baseAsset: ReturnType<typeof getAssetFromAssetList>;
}
>
> {
const orderbookAddresses = [
...new Set(historicalOrders.map(({ contract }) => contract)),
];

const promises = orderbookAddresses.map(async (orderbookAddress) => {
const denoms = await getOrderbookDenoms({
orderbookAddress,
assetLists,
chainList,
});
return [orderbookAddress, denoms];
});

const orderbookDenoms: Record<
string,
{
quoteAsset: ReturnType<typeof getAssetFromAssetList>;
baseAsset: ReturnType<typeof getAssetFromAssetList>;
}
> = {};
const orderbookDenomsArray = await Promise.all(promises);

for (let i = 0; i < orderbookDenomsArray.length; i++) {
const [contract, denoms]: any = orderbookDenomsArray[i];
orderbookDenoms[contract] = denoms;
}

return orderbookDenoms;
}

/**
* Data returned from the Numia query does not exactly match the interface used by the webapp.
* This function maps the Numia data to the webapp interface.
*/
async function mapHistoricalToMapped(
historicalOrders: HistoricalLimitOrder[],
userAddress: string,
assetLists: AssetList[],
chainList: Chain[]
): Promise<MappedLimitOrder[]> {
const orderbookDenoms = await getRelevantOrderbookDenoms(
historicalOrders,
assetLists,
chainList
);
return historicalOrders.map((o) => {
const { quoteAsset, baseAsset } = orderbookDenoms[o.contract];
const quantityMin = parseInt(o.quantity);
const placedQuantityMin = parseInt(o.quantity);
const price = tickToPrice(new Int(o.tick_id));
const percentClaimed = new Dec(1);
const output =
o.order_direction === "bid"
? new Dec(placedQuantityMin).quo(price)
: new Dec(placedQuantityMin).mul(price);

const normalizationFactor = new Dec(10).pow(
new Int((quoteAsset?.decimals ?? 0) - (baseAsset?.decimals ?? 0))
);

return {
quoteAsset,
baseAsset,
etas: "0",
order_direction: o.order_direction,
order_id: parseInt(o.order_id),
owner: userAddress,
placed_at:
dayjs(
o.place_timestamp && o.place_timestamp.length > 0
? o.place_timestamp
: 0
).unix() * 1000,
placed_quantity: parseInt(o.quantity),
placedQuantityMin,
quantityMin,
quantity: parseInt(o.quantity),
price: price.quo(normalizationFactor),
status: o.status as OrderStatus,
tick_id: parseInt(o.tick_id),
output,
percentClaimed,
percentFilled: new Dec(1),
totalFilled: parseInt(o.quantity),
orderbookAddress: o.contract,
};
});
}
1 change: 1 addition & 0 deletions packages/server/src/queries/complex/orderbooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from "./maker-fee";
export * from "./orderbook-state";
export * from "./pools";
export * from "./tick-state";
export * from "./types";
4 changes: 2 additions & 2 deletions packages/server/src/queries/complex/orderbooks/tick-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function getOrderbookTickState({
key: `orderbookTickInfo-${orderbookAddress}-${tickIds
.sort((a, b) => a - b)
.join(",")}`,
ttl: 1000 * 3, // 3 seconds
ttl: 1000 * 2, // 3 seconds
getFreshValue: () =>
queryOrderbookTicks({ orderbookAddress, chainList, tickIds }).then(
({ data }) => data.ticks
Expand All @@ -46,7 +46,7 @@ export function getOrderbookTickUnrealizedCancels({
key: `orderbookTickUnrealizedCancels-${orderbookAddress}-${tickIds
.sort((a, b) => a - b)
.join(",")}`,
ttl: 1000 * 3, // 3 seconds
ttl: 1000 * 2, // 3 seconds
getFreshValue: () =>
queryOrderbookTickUnrealizedCancelsById({
orderbookAddress,
Expand Down
Loading

0 comments on commit e3d40f4

Please sign in to comment.