You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

205 lines
8.1 KiB

import {
Box,
Button,
Container,
Divider,
Grid,
GridItem,
HStack,
Switch,
Tab,
TabList,
TabPanel,
TabPanels,
Tabs,
Text,
useToast,
VStack,
} from "@chakra-ui/react";
import React, { useEffect, useState } from "react";
import { useAsync } from "react-async";
import { useEventSource } from "react-sse-hooks";
import { CfdTable } from "./components/cfdtables/CfdTable";
import ConnectedTakers, { TakerId } from "./components/ConnectedTakers";
import CurrencyInputField from "./components/CurrencyInputField";
import CurrentPrice from "./components/CurrentPrice";
import createErrorToast from "./components/ErrorToast";
import useLatestEvent from "./components/Hooks";
import OrderTile from "./components/OrderTile";
import { Cfd, intoCfd, intoOrder, Order, PriceInfo, StateGroupKey, WalletInfo } from "./components/Types";
import Wallet from "./components/Wallet";
import { CfdSellOrderPayload, postCfdSellOrderRequest } from "./MakerClient";
const SPREAD = 1.01;
export default function App() {
document.title = "Hermes Maker";
let source = useEventSource({ source: "/api/feed", options: { withCredentials: true } });
const cfdsOrUndefined = useLatestEvent<Cfd[]>(source, "cfds", intoCfd);
let cfds = cfdsOrUndefined ? cfdsOrUndefined! : [];
const order = useLatestEvent<Order>(source, "order", intoOrder);
const walletInfo = useLatestEvent<WalletInfo>(source, "wallet");
const priceInfo = useLatestEvent<PriceInfo>(source, "quote");
const takersOrUndefined = useLatestEvent<TakerId[]>(source, "takers");
let takers = takersOrUndefined || [];
const toast = useToast();
let [minQuantity, setMinQuantity] = useState<string>("100");
let [maxQuantity, setMaxQuantity] = useState<string>("1000");
let [orderPrice, setOrderPrice] = useState<string>("0");
let [autoRefresh, setAutoRefresh] = useState(true);
useEffect(() => {
if (autoRefresh && priceInfo) {
setOrderPrice((priceInfo.ask * SPREAD).toFixed(2).toString());
}
}, [priceInfo, autoRefresh]);
let { run: makeNewCfdSellOrder, isLoading: isCreatingNewCfdOrder } = useAsync({
deferFn: async ([payload]: any[]) => {
try {
await postCfdSellOrderRequest(payload as CfdSellOrderPayload);
} catch (e) {
createErrorToast(toast, e);
}
},
});
const pendingOrders = cfds.filter((value) => value.state.getGroup() === StateGroupKey.PENDING_ORDER);
const pendingSettlements = cfds.filter((value) => value.state.getGroup() === StateGroupKey.PENDING_SETTLEMENT);
const pendingRollOvers = cfds.filter((value) => value.state.getGroup() === StateGroupKey.PENDING_ROLL_OVER);
const opening = cfds.filter((value) => value.state.getGroup() === StateGroupKey.OPENING);
const open = cfds.filter((value) => value.state.getGroup() === StateGroupKey.OPEN);
const closed = cfds.filter((value) => value.state.getGroup() === StateGroupKey.CLOSED);
return (
<Container maxWidth="120ch" marginTop="1rem">
<HStack spacing={5}>
<VStack>
<Wallet walletInfo={walletInfo} />
<Grid
gridTemplateColumns="max-content auto"
padding={5}
rowGap={5}
columnGap={5}
shadow="md"
width="100%"
alignItems="center"
>
<Text align={"left"}>Reference Price:</Text>
<CurrentPrice priceInfo={priceInfo} />
<Text>Min Quantity:</Text>
<CurrencyInputField
onChange={(valueString: string) => setMinQuantity(parse(valueString))}
value={format(minQuantity)}
/>
<Text>Max Quantity:</Text>
<CurrencyInputField
onChange={(valueString: string) => setMaxQuantity(parse(valueString))}
value={format(maxQuantity)}
/>
<Text>Offer Price:</Text>
<HStack>
<CurrencyInputField
onChange={(valueString: string) => {
setOrderPrice(parse(valueString));
setAutoRefresh(false);
}}
value={format(orderPrice)}
/>
<HStack>
<Switch
id="auto-refresh"
isChecked={autoRefresh}
onChange={() => setAutoRefresh(!autoRefresh)}
/>
<Text>Auto-refresh</Text>
</HStack>
</HStack>
<Text>Leverage:</Text>
<HStack spacing={5}>
<Button disabled={true}>x1</Button>
<Button colorScheme="blue" variant="solid">x{2}</Button>
<Button disabled={true}>x5</Button>
</HStack>
<GridItem colSpan={2}>
<Divider colSpan={2} />
</GridItem>
<GridItem colSpan={2} textAlign="center">
<Button
disabled={isCreatingNewCfdOrder || orderPrice === "0"}
variant={"solid"}
colorScheme={"blue"}
onClick={() => {
let payload: CfdSellOrderPayload = {
price: Number.parseFloat(orderPrice),
min_quantity: Number.parseFloat(minQuantity),
max_quantity: Number.parseFloat(maxQuantity),
};
makeNewCfdSellOrder(payload);
}}
>
{order ? "Update Sell Order" : "Create Sell Order"}
</Button>
</GridItem>
</Grid>
</VStack>
<VStack>
<ConnectedTakers takers={takers} />
{order && <OrderTile order={order} />}
</VStack>
<Box width="40%" />
</HStack>
<Tabs marginTop={5}>
<TabList>
<Tab>Open [{open.length}]</Tab>
<Tab>Pending Orders [{pendingOrders.length}]</Tab>
<Tab>Pending Settlements [{pendingSettlements.length}]</Tab>
<Tab>Pending Roll Overs [{pendingRollOvers.length}]</Tab>
<Tab>Opening [{opening.length}]</Tab>
<Tab>Closed [{closed.length}]</Tab>
</TabList>
<TabPanels>
<TabPanel>
<CfdTable data={open} />
</TabPanel>
<TabPanel>
<CfdTable data={pendingOrders} />
</TabPanel>
<TabPanel>
<CfdTable data={pendingSettlements} />
</TabPanel>
<TabPanel>
<CfdTable data={pendingRollOvers} />
</TabPanel>
<TabPanel>
<CfdTable data={opening} />
</TabPanel>
<TabPanel>
<CfdTable data={closed} />
</TabPanel>
</TabPanels>
</Tabs>
</Container>
);
}
function format(val: any) {
return `$` + val;
}
function parse(val: any) {
return val.replace(/^\$/, "");
}