Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Limit orders #2228

Merged
merged 50 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
55adb04
fetch and display orders
Robiquet Jan 25, 2024
bad2932
mock up limit order form
Robiquet Jan 25, 2024
9293852
update tabs and add order type selector
Robiquet Jan 26, 2024
92ff18f
close on blur
Robiquet Jan 26, 2024
275bf83
get tabs working
Robiquet Jan 26, 2024
0ec3098
adjust colours
Robiquet Jan 26, 2024
8c72de6
adjust button
Robiquet Jan 29, 2024
b4edcbd
refactor
Robiquet Jan 29, 2024
9f010d5
inprogress
Robiquet Jan 31, 2024
63d5bc6
misc
Robiquet Jan 31, 2024
0a0ea0b
spot prices
Robiquet Feb 5, 2024
e0e5a98
buy form
Robiquet Feb 5, 2024
59a56e3
sell order form
Robiquet Feb 5, 2024
5664370
correct copy
Robiquet Feb 5, 2024
7dfbcfc
Merge branch 'staging' of https://github.com/zeitgeistpm/ui into tr-o…
Robiquet Feb 28, 2024
ad179ba
adjust styling
Robiquet Feb 28, 2024
bd26087
add aditional info
Robiquet Feb 29, 2024
b17b09d
Merge branch 'staging' of https://github.com/zeitgeistpm/ui into tr-o…
Robiquet Mar 6, 2024
d54034f
upgrade sdk
Robiquet Mar 7, 2024
30a5aa0
map indexer orders
Robiquet Mar 8, 2024
b757ccf
order cancelling
Robiquet Mar 8, 2024
86d27ad
make table more generic and add to portfolio page
Robiquet Mar 8, 2024
b003781
filter orders
Robiquet Mar 11, 2024
8dc40a5
order selection function and test
Robiquet Mar 11, 2024
e913826
Merge branch 'staging' of https://github.com/zeitgeistpm/ui into tr-o…
Robiquet Apr 24, 2024
74ff2e0
missing yarn.lock change
Robiquet Apr 24, 2024
7df5e01
add limit buy and sell txs
Robiquet Apr 25, 2024
d8e3549
update for new order schema
Robiquet Apr 26, 2024
e2b97a4
remove unused arg
Robiquet Apr 26, 2024
8488161
add router for buy form
Robiquet Apr 26, 2024
5dbd889
sell form
Robiquet Apr 26, 2024
f394712
handle form submission and loading
Robiquet Apr 29, 2024
d2db865
Merge branch 'tr-more-asset-refactor' of https://github.com/zeitgeist…
Robiquet May 6, 2024
f4b8c20
adjustment buy/sell and order display
Robiquet May 6, 2024
1e82b3f
price offset
Robiquet May 6, 2024
d70c406
adjust order routing. add filled percentage
Robiquet May 7, 2024
83d5d4d
filter filled trades
Robiquet May 8, 2024
84a7324
add price after trade calcs
Robiquet May 8, 2024
24d92b6
adjust price after trade calcs
Robiquet May 9, 2024
a1fd021
table change
Robiquet May 10, 2024
09e656e
add query invalidation, fix defalt limit pricing
Robiquet May 10, 2024
f3ece8f
Merge branch 'staging' of https://github.com/zeitgeistpm/ui into tr-o…
Robiquet May 14, 2024
da7a63e
remove id
Robiquet May 14, 2024
c8f012e
show fees. hide order if there are none
Robiquet May 14, 2024
830bb03
only route to "placed" orders
Robiquet May 17, 2024
2e151b7
Merge branch 'tr-remove-avatar' of https://github.com/zeitgeistpm/ui …
Robiquet May 17, 2024
08b2b78
display and handle order status
Robiquet May 17, 2024
1b8753d
only show my orders when wallet is connected
Robiquet May 22, 2024
dd75405
Merge branch 'staging' of https://github.com/zeitgeistpm/ui into tr-o…
Robiquet Jun 19, 2024
b15a0ef
fix isloading check
Robiquet Jun 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions components/orderbook/OrdersTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { useQueryClient } from "@tanstack/react-query";
import { InputMaybe, OrderStatus, OrderWhereInput } from "@zeitgeistpm/indexer";
import { BaseAssetId, ZTG, getIndexOf, isRpcSdk } from "@zeitgeistpm/sdk";
import SecondaryButton from "components/ui/SecondaryButton";
import Table, { TableColumn, TableData } from "components/ui/Table";
import { lookupAssetSymbol } from "lib/constants/foreign-asset";
import {
ordersRootKey,
useOrders,
} from "lib/hooks/queries/orderbook/useOrders";
import { useMarketsByIds } from "lib/hooks/queries/useMarketsByIds";
import { useExtrinsic } from "lib/hooks/useExtrinsic";
import { useSdkv2 } from "lib/hooks/useSdkv2";
import { useNotifications } from "lib/state/notifications";
import { useWallet } from "lib/state/wallet";
import { parseAssetIdString } from "lib/util/parse-asset-id";

const columns: TableColumn[] = [
{
header: "Outcome",
accessor: "outcome",
type: "component",
},
{
header: "Side",
accessor: "side",
type: "text",
},
{
header: "Amount",
accessor: "amount",
type: "text",
},
{
header: "Price",
accessor: "price",
type: "text",
},
{
header: "Filled",
accessor: "percentageFilled",
type: "text",
},
{
header: "Status",
accessor: "status",
type: "text",
},
{
header: "",
accessor: "button",
type: "component",
width: "180px",
},
];

const OrdersTable = ({ where }: { where: InputMaybe<OrderWhereInput> }) => {
const { realAddress } = useWallet();
const { data: orders } = useOrders(where);
const { data: markets } = useMarketsByIds(
orders?.map((order) => ({ marketId: order.marketId })),
);

const tableData: TableData[] | undefined = orders?.map(
({
side,
price,
outcomeAssetId,
outcomeAmount,
id,
marketId,
makerAddress,
filledPercentage,
status,
}) => {
const index = getIndexOf(outcomeAssetId);
const market = markets?.find((market) => market.marketId === marketId);
const outcomeName = market?.categories?.[index]?.name;
const baseAsset = parseAssetIdString(market?.baseAsset) as BaseAssetId;
const baseSymbol = lookupAssetSymbol(baseAsset);
const orderFilled = filledPercentage === 100;

return {
side: side.toUpperCase(),
outcome: outcomeName,
amount: outcomeAmount.div(ZTG).toFixed(2),
value: `${outcomeAmount.mul(price).div(ZTG).toFixed(3)} ${baseSymbol}`,
price: `${price.toFixed(3)} ${baseSymbol}`,
percentageFilled: `${filledPercentage.toFixed(0)}%`,
status: status,
button: (
<CancelOrderButton
orderId={id}
disabled={
realAddress !== makerAddress ||
orderFilled ||
status === OrderStatus.Removed
}
/>
),
};
},
);
return (
<div>
<Table columns={columns} data={tableData} showHighlight={false} />
</div>
);
};

const CancelOrderButton = ({
orderId,
disabled,
}: {
orderId: string;
disabled: boolean;
}) => {
const notificationStore = useNotifications();
const [sdk, id] = useSdkv2();
const queryClient = useQueryClient();

const {
isLoading,
isSuccess,
send: cancelOrder,
} = useExtrinsic(
() => {
if (!isRpcSdk(sdk)) return;
return sdk.api.tx.orderbook.removeOrder(orderId);
},
{
onSuccess: () => {
queryClient.invalidateQueries([id, ordersRootKey]);

notificationStore.pushNotification("Successfully cancelled order", {
type: "Success",
});
},
},
);

return (
<SecondaryButton
onClick={() => cancelOrder()}
disabled={isLoading || isSuccess || disabled}
>
Cancel Order
</SecondaryButton>
);
};

export default OrdersTable;
183 changes: 141 additions & 42 deletions components/trade-form/Amm2TradeForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Tab } from "@headlessui/react";
import { MarketOutcomeAssetId, getIndexOf, ZTG } from "@zeitgeistpm/sdk";
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import BuyForm from "./BuyForm";
import SellForm from "./SellForm";
import TradeTab, { TradeTabType } from "./TradeTab";
Expand All @@ -10,6 +10,11 @@ import Decimal from "decimal.js";
import { useMarket } from "lib/hooks/queries/useMarket";
import { useAssetMetadata } from "lib/hooks/queries/useAssetMetadata";
import { parseAssetIdString } from "lib/util/parse-asset-id";
import LimitOrderForm, {
LimitBuyOrderForm,
LimitSellOrderForm,
} from "./LimitOrderForm";
import { ChevronDown } from "react-feather";

const Amm2TradeForm = ({
marketId,
Expand All @@ -23,6 +28,7 @@ const Amm2TradeForm = ({
showTabs?: boolean;
}) => {
const [tabType, setTabType] = useState<TradeTabType>();
const [orderType, setOrderType] = useState<OrderType>("market");
const [showSuccessBox, setShowSuccessBox] = useState(false);
const [amountReceived, setAmountReceived] = useState<Decimal>();
const [amountIn, setAmountIn] = useState<Decimal>();
Expand Down Expand Up @@ -87,54 +93,147 @@ const Amm2TradeForm = ({
}}
selectedIndex={tabType}
>
<Tab.List
className={`h-[71px] text-center text-ztg-18-150 font-medium ${
showTabs ? "flex" : "hidden"
}`}
>
<Tab
as={TradeTab}
selected={tabType === TradeTabType.Buy}
className="rounded-tl-[10px]"
>
Buy
</Tab>
<Tab
as={TradeTab}
selected={tabType === TradeTabType.Sell}
className="rounded-tr-[10px]"
<div className="flex">
<Tab.List
className={`h-[51px] w-[75%] text-center text-ztg-18-150 font-medium ${
showTabs ? "flex" : "hidden"
}`}
>
Sell
</Tab>
</Tab.List>
<Tab
as={TradeTab}
selected={tabType === TradeTabType.Buy}
className="rounded-tl-[10px]"
>
Buy
</Tab>
<Tab
as={TradeTab}
selected={tabType === TradeTabType.Sell}
className="rounded-tr-[10px]"
>
Sell
</Tab>
</Tab.List>
<OrderTypeSelector
onTypeSelected={(type) => {
setOrderType(type);
}}
value={orderType}
/>
</div>
<Tab.Panels className="p-[30px]">
<Tab.Panel>
<BuyForm
marketId={marketId}
initialAsset={initialAsset}
onSuccess={(data, asset, amount) => {
handleSuccess(data);
setOutcomeAsset(asset);
setAmountIn(amount);
}}
/>
</Tab.Panel>
<Tab.Panel>
<SellForm
marketId={marketId}
initialAsset={initialAsset}
onSuccess={(data, asset, amount) => {
handleSuccess(data);
setOutcomeAsset(asset);
setAmountIn(amount);
}}
/>
</Tab.Panel>
{orderType === "market" ? (
<>
<Tab.Panel>
<BuyForm
marketId={marketId}
initialAsset={initialAsset}
onSuccess={(data, asset, amount) => {
handleSuccess(data);
setOutcomeAsset(asset);
setAmountIn(amount);
}}
/>
</Tab.Panel>
<Tab.Panel>
<SellForm
marketId={marketId}
initialAsset={initialAsset}
onSuccess={(data, asset, amount) => {
handleSuccess(data);
setOutcomeAsset(asset);
setAmountIn(amount);
}}
/>
</Tab.Panel>
</>
) : (
<>
<Tab.Panel>
<LimitBuyOrderForm
marketId={marketId}
initialAsset={initialAsset}
/>
</Tab.Panel>
<Tab.Panel>
<LimitSellOrderForm
marketId={marketId}
initialAsset={initialAsset}
/>
</Tab.Panel>
</>
)}
</Tab.Panels>
</Tab.Group>
)}
</>
);
};

type OrderType = "market" | "limit";

const OrderTypeSelector = ({
onTypeSelected,
value,
}: {
onTypeSelected: (type: OrderType) => void;
value: OrderType;
}) => {
const [menuOpen, setMenuOpen] = useState(false);
const wrapperRef = useRef<HTMLDivElement>(null);

const handleTypeClick = (type: OrderType) => {
onTypeSelected(type);
setMenuOpen(false);
};

useEffect(() => {
const handleClickOutside = (event) => {
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
setMenuOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [wrapperRef]);

return (
<div className="relative flex w-[25%] items-center justify-center">
<button
onClick={() => setMenuOpen((open) => !open)}
className="flex w-full items-center justify-center px-5"
>
<div>{value === "market" ? "Market" : "Limit"}</div>
<ChevronDown className="ml-auto" size={16} />
</button>

{menuOpen && (
<div
ref={wrapperRef}
className="absolute top-[52px] flex w-32 flex-col gap-y-3 rounded-lg bg-white p-4 shadow-[0px_4px_20px_0px_#00000040]"
>
<button
className={`${
value === "market" ? "font-medium text-black" : "text-sky-600"
} `}
onClick={() => handleTypeClick("market")}
>
Market
</button>
<button
className={`${
value === "limit" ? "font-medium text-black" : "text-sky-600"
} `}
onClick={() => handleTypeClick("limit")}
>
Limit
</button>
</div>
)}
</div>
);
};

export default Amm2TradeForm;
Loading
Loading