Skip to content

Commit

Permalink
websocket support (#999)
Browse files Browse the repository at this point in the history
* websocket

* lint

* object keys

* slight change add liquidations

* use same component as other liq

* go -1 rather than home

* remove await

---------

Co-authored-by: jmzwar <[email protected]>
  • Loading branch information
jmzwar and jmzwar authored Oct 29, 2023
1 parent ecb3e34 commit 30ca681
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
MarkPrice,
PercentageChange,
WalletTooltip,
Currency,
} from '../Shared';
import { OpenPositionsLoading } from './OpenPositionsLoading';
import { usePositions, PositionType } from '../../hooks';
Expand Down Expand Up @@ -58,7 +59,7 @@ export const OpenPositionsTable = () => {
<TableHeaderCell>Mark Price</TableHeaderCell>
<TableHeaderCell>Size</TableHeaderCell>
<TableHeaderCell>Unrealized PNL</TableHeaderCell>
<TableHeaderCell>ROI</TableHeaderCell>
<TableHeaderCell>Liquidation Price</TableHeaderCell>
<TableHeaderCell>Realized PNL</TableHeaderCell>
<TableHeaderCell>Address</TableHeaderCell>
</Tr>
Expand All @@ -84,6 +85,7 @@ export const OpenPositionsTable = () => {
address,
marketPrice,
unrealizedPnlPercentage,
liquidationPrice,
}: PositionType,
index: number
) => {
Expand All @@ -106,7 +108,10 @@ export const OpenPositionsTable = () => {
pnl={unrealizedPnl.toNumber()}
pnlPercentage={unrealizedPnlPercentage.toNumber()} //
/>
{/* Unrealized ROI */}
{/* Liquidation Price */}
{/* Liquidation Price */}
<Currency amount={liquidationPrice.toNumber()} />

<PercentageChange amount={unrealizedPnlPercentage.toNumber()} />
{/* Realized PNL */}
<PnL pnl={realizedPnl.toNumber()} />
Expand Down
12 changes: 5 additions & 7 deletions v2/perps-v2/ui/src/hooks/useLargestOpenPosition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as z from 'zod';
import { scale } from '../utils';
import { utils } from 'ethers';
import { wei } from '@synthetixio/wei';
import { pyth, getMarketsPythConfig } from '../utils/pyth';
import { getMarketsPythConfig, prices, PythPrice } from '../utils/pyth';

const pythItemSchema = z.object({
pythId: z.union([z.string(), z.undefined()]),
Expand Down Expand Up @@ -84,14 +84,12 @@ export function useLargestOpenPosition() {
const sizeQuery = generateOpenPositionsQuery(marketIds);
const pythIds = marketPyth.map((item) => item?.pythId || '').filter((item) => item !== '');

const [{ data: sizeData }, result] = await Promise.all([
await client.query({ query: sizeQuery }),
await pyth.getLatestPriceFeeds(pythIds),
]);
const { data: sizeData } = await client.query({ query: sizeQuery });

// Attribute the pyth result to the market
const hydratedPythResult = result?.map((item, index) => {
const price = item?.getPriceUnchecked();
const hydratedPythResult = marketPyth?.map((item, index) => {
const price: PythPrice = prices[pythIds[index].substring(2)];

return {
...price,
pythId: marketPyth[index]?.pythId || '',
Expand Down
15 changes: 7 additions & 8 deletions v2/perps-v2/ui/src/hooks/useMarkets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import {
calculateMarkPrice,
getMarketsPythConfig,
PythConfigByMarketKey,
pyth,
scale,
initMulticall,
initPerpsMarketData,
prices,
} from '../utils';
import { PerpsV2MarketData } from '@synthetixio/contracts/build/mainnet-ovm/deployment/PerpsV2MarketData';
import { ZodStringToWei } from './useLargestOpenPosition';
Expand Down Expand Up @@ -152,10 +152,9 @@ export async function fetchMarkets(
return { pythId: pythInfo.pythId, ...item };
});

const [multiCallResponse, indexPrices] = await Promise.all([
multicall.callStatic.aggregate(marketDetailCalls.concat(allMarketSummaries)),
pyth.getLatestPriceFeeds([...dataWithPythId.map(({ pythId }) => pythId)]),
]);
const multiCallResponse = await multicall.callStatic.aggregate(
marketDetailCalls.concat(allMarketSummaries)
);

const marketDetailsData = multiCallResponse.returnData.slice(0, marketDetailCalls.length);
const allMarketSummariesData = multiCallResponse.returnData.slice(marketDetailCalls.length);
Expand All @@ -170,7 +169,7 @@ export async function fetchMarkets(
);

return dataWithPythId
.map(({ market, percentageDifference, volume }, index) => {
.map(({ market, percentageDifference, volume, pythId }, index) => {
const marketDetails = allMarketSummariesDataDecoded.flat()?.find((item) => {
return item.key === market.marketKey;
}) as PerpsV2MarketData.MarketSummaryStructOutput;
Expand All @@ -179,8 +178,8 @@ export async function fetchMarkets(

// Get the index price from pyth
let indexPrice: Wei = wei(0);
if (indexPrices && indexPrices[index]) {
const rawPriceInfo = indexPrices[index].getPriceUnchecked();
if (prices) {
const rawPriceInfo = prices[pythId?.substring(2)];
indexPrice = scale(wei(rawPriceInfo?.price), rawPriceInfo?.expo || 1);
}

Expand Down
23 changes: 13 additions & 10 deletions v2/perps-v2/ui/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Dashboard, Actions, Markets, Positions, StatsV3 } from './pages';
import { isStaging } from './utils/isStaging';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { EthersProvider } from './utils/ProviderContext';
import { PythRealtimePrices } from './utils/pyth';

const client = new ApolloClient({
uri: isStaging ? PERPS_V2_DASHBOARD_GRAPH_GOERLI_URL : PERPS_V2_DASHBOARD_GRAPH_URL,
Expand Down Expand Up @@ -111,14 +112,16 @@ const customTheme = extendTheme({
});

root.render(
<EthersProvider>
<ApolloProvider client={client}>
<QueryClientProvider client={queryClient}>
<ChakraProvider theme={customTheme}>
<Fonts />
<RouterProvider router={router} />
</ChakraProvider>
</QueryClientProvider>
</ApolloProvider>
</EthersProvider>
<PythRealtimePrices>
<EthersProvider>
<ApolloProvider client={client}>
<QueryClientProvider client={queryClient}>
<ChakraProvider theme={customTheme}>
<Fonts />
<RouterProvider router={router} />
</ChakraProvider>
</QueryClientProvider>
</ApolloProvider>
</EthersProvider>
</PythRealtimePrices>
);
4 changes: 2 additions & 2 deletions v2/perps-v2/ui/src/pages/Account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ export const Account: FC = () => {
<Button
variant="ghost"
fontWeight="700"
onClick={() => navigate('/')}
onClick={() => navigate(-1)}
leftIcon={<ArrowBackIcon />}
>
Home
Back
</Button>
</Box>
<Link
Expand Down
16 changes: 4 additions & 12 deletions v2/perps-v2/ui/src/queries/resolved.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Resolvers, gql } from '@apollo/client';
import { EvmPriceServiceConnection } from '@pythnetwork/pyth-evm-js';
import Wei, { wei } from '@synthetixio/wei';
import { utils } from 'ethers';
import { fetchPositions } from '../hooks';
import { notNill } from '../utils/notNil';
import { scale, calculatePositionData, getMarketsPythConfig } from '../utils';
import { scale, calculatePositionData, getMarketsPythConfig, prices } from '../utils';

export const POSITIONS_CONTRACT_QUERY = gql(`
query ($openPositions: PositionsMarketQuery) {
Expand Down Expand Up @@ -38,8 +37,6 @@ export const typeDefs = gql(`
}
`);

const pyth = new EvmPriceServiceConnection('https://xc-mainnet.pyth.network');

export const resolvers: Resolvers | Resolvers[] = {
Query: {
positionsFromContract: async (
Expand All @@ -51,18 +48,13 @@ export const resolvers: Resolvers | Resolvers[] = {
const positionsData = await fetchPositions(openPositions, provider);
const offchainPrices: { asset: string; price: Wei }[] = [];
const pythConfigByMarketKey = await getMarketsPythConfig();

await Promise.all(
openPositions.map(async ({ market, asset }: { market: string; asset: string }) => {
const marketId = utils.parseBytes32String(market);

const feedData = await pyth.getLatestPriceFeeds([
`${pythConfigByMarketKey[marketId].pythId}`,
]);

if (feedData && feedData.length > 0) {
const price = feedData[0].getPriceUnchecked();
offchainPrices.push({ asset, price: scale(wei(price.price), price.expo) });
}
const price = prices[pythConfigByMarketKey[marketId].pythId.substring(2)];
offchainPrices.push({ asset, price: scale(wei(price.price), price.expo) });
})
);

Expand Down
1 change: 1 addition & 0 deletions v2/perps-v2/ui/src/utils/calculations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export function calculateLeverage(size: Wei, markPrice: Wei, margin: Wei) {
if (size.eq(0)) return wei(0);
return size.mul(markPrice).div(margin).abs();
}

export const calculateMarkPrice = (
pythPrice: Wei | undefined,
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { z } from 'zod';
import { ReactNode, useEffect } from 'react';
import { isStaging } from './isStaging';
import { EvmPriceServiceConnection } from '@pythnetwork/pyth-evm-js';
import { MarketsByKey } from '../types';
Expand All @@ -13,6 +14,7 @@ const OffchainFeedSchema = z.array(
feedId: z.string(),
})
);

export type PythConfigByMarketKey = Record<
string,
{
Expand All @@ -21,6 +23,7 @@ export type PythConfigByMarketKey = Record<
asset: string;
}
>;

const formatAssetToPerpName = (x: string) => {
if (x === 'sETH' || x === 'sBTC') {
return `${x}PERP`;
Expand Down Expand Up @@ -51,3 +54,38 @@ export const getMarketsPythConfig = () => {
}, {});
});
};

export interface PythPrice {
price: string;
conf: string;
expo: number;
publishTime: number;
}

export const prices: { [key: string]: PythPrice } = {};

export const PythRealtimePrices = ({ children }: { children: ReactNode }) => {
useEffect(() => {
(async () => {
const pythConfigByMarketKey = await getMarketsPythConfig();
const pythIds = Object.values(pythConfigByMarketKey).map((x) => x.pythId);
await pyth.subscribePriceFeedUpdates(pythIds, (price) => {
const { id } = price;
const priceData = price.getPriceUnchecked();

if (priceData) {
prices[id] = priceData;
}
});
})();

return () => {
// Clean up WebSocket connection when the component unmounts
(() => {
pyth.unsubscribePriceFeedUpdates(Object.keys(prices));
})();
};
}, []);

return <>{children}</>;
};

1 comment on commit 30ca681

@vercel
Copy link

@vercel vercel bot commented on 30ca681 Oct 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

v2-storybook – ./v2/ui

v2-storybook-git-master-synthetixio.vercel.app
v2-storybook-synthetixio.vercel.app
staking-storybook.vercel.app

Please sign in to comment.