From 3fdb08b6fec82fb7e9797ec7569ddf7f273a8128 Mon Sep 17 00:00:00 2001 From: Nikita Date: Mon, 1 Aug 2022 15:29:02 +0800 Subject: [PATCH 1/2] Pull liquidation info from the new subgraph --- components/BannerManager/BannerManager.tsx | 44 ++-------- .../Liquidation/LiquidationBanner.tsx | 47 +++++++++++ components/BannerManager/Liquidation/index.ts | 3 + .../Liquidation/useLiquidation.ts | 34 ++++++++ .../useLiquidationOptimismSubgraph.ts | 81 +++++++++++++++++++ 5 files changed, 172 insertions(+), 37 deletions(-) create mode 100644 components/BannerManager/Liquidation/LiquidationBanner.tsx create mode 100644 components/BannerManager/Liquidation/index.ts create mode 100644 components/BannerManager/Liquidation/useLiquidation.ts create mode 100644 components/BannerManager/Liquidation/useLiquidationOptimismSubgraph.ts diff --git a/components/BannerManager/BannerManager.tsx b/components/BannerManager/BannerManager.tsx index 1ff199f38..648ced2d2 100644 --- a/components/BannerManager/BannerManager.tsx +++ b/components/BannerManager/BannerManager.tsx @@ -5,62 +5,32 @@ import styled from 'styled-components'; import Banner, { BannerType } from 'sections/shared/Layout/Banner'; import { LOCAL_STORAGE_KEYS } from 'constants/storage'; import { ExternalLink } from 'styles/common'; -import { formatShortDateWithTime } from 'utils/formatters/date'; -import { wei } from '@synthetixio/wei'; import useSynthetixQueries from '@synthetixio/queries'; import { EXTERNAL_LINKS } from 'constants/links'; import Connector from 'containers/Connector'; import { isAnyElectionInNomination, isAnyElectionInVoting } from 'utils/governance'; +import { useLiquidation, LiquidationBanner } from './Liquidation'; const BannerManager: FC = () => { - const { subgraph, useGetLiquidationDataQuery, useGetDebtDataQuery, useGetElectionsPeriodStatus } = - useSynthetixQueries(); + const { subgraph, useGetElectionsPeriodStatus } = useSynthetixQueries(); const { L2DefaultProvider, isL2, walletAddress } = Connector.useContainer(); const periodStatusQuery = useGetElectionsPeriodStatus(L2DefaultProvider); const electionIsInNomination = isAnyElectionInNomination(periodStatusQuery.data); const electionIsInVoting = isAnyElectionInVoting(periodStatusQuery.data); - const liquidationData = useGetLiquidationDataQuery(walletAddress); - const debtData = useGetDebtDataQuery(walletAddress); - const feeClaims = subgraph.useGetFeesClaimeds( { first: 1, where: { account: walletAddress?.toLowerCase() } }, { timestamp: true, value: true, rewards: true } ); - - const issuanceRatio = debtData?.data?.targetCRatio ?? wei(0); - const cRatio = debtData?.data?.currentCRatio ?? wei(0); - const liquidationDeadlineForAccount = - liquidationData?.data?.liquidationDeadlineForAccount ?? wei(0); - - const issuanceRatioPercentage = issuanceRatio.eq(0) ? 0 : 100 / Number(issuanceRatio); - const hasClaimHistory = !!feeClaims.data?.length; - if (!liquidationDeadlineForAccount.eq(0) && cRatio.gt(issuanceRatio)) { - return ( - , - , - , - ]} - /> - } - /> - ); + const { deadline, hasWarning, ratio } = useLiquidation(); + + if (hasWarning) { + return ; } + if (electionIsInVoting) { return ( = ({ ratio, deadline }) => { + return ( + , + , + , + ]} + /> + } + /> + ); +}; + +const Strong = styled.strong` + font-family: ${(props) => props.theme.fonts.condensedBold}; +`; + +const StyledExternalLink = styled(ExternalLink)` + color: ${(props) => props.theme.colors.white}; + text-decoration: underline; + &:hover { + text-decoration: underline; + } +`; diff --git a/components/BannerManager/Liquidation/index.ts b/components/BannerManager/Liquidation/index.ts new file mode 100644 index 000000000..ec5bc8ada --- /dev/null +++ b/components/BannerManager/Liquidation/index.ts @@ -0,0 +1,3 @@ +export * from './useLiquidation'; +export * from './useLiquidationOptimismSubgraph'; +export * from './LiquidationBanner'; diff --git a/components/BannerManager/Liquidation/useLiquidation.ts b/components/BannerManager/Liquidation/useLiquidation.ts new file mode 100644 index 000000000..b86f24bfe --- /dev/null +++ b/components/BannerManager/Liquidation/useLiquidation.ts @@ -0,0 +1,34 @@ +import { wei } from '@synthetixio/wei'; +import useSynthetixQueries from '@synthetixio/queries'; +import Connector from 'containers/Connector'; +import { useLiquidationOptimismSubgraph } from './useLiquidationOptimismSubgraph'; + +export function useLiquidation() { + const { useGetLiquidationDataQuery, useGetDebtDataQuery } = useSynthetixQueries(); + const { walletAddress } = Connector.useContainer(); + + const liquidationData = useGetLiquidationDataQuery(walletAddress); + const debtData = useGetDebtDataQuery(walletAddress); + + const issuanceRatio = debtData?.data?.targetCRatio ?? wei(0); + const cRatio = debtData?.data?.currentCRatio ?? wei(0); + const liquidationDeadlineForAccount = + liquidationData?.data?.liquidationDeadlineForAccount ?? wei(0); + + const ratio = issuanceRatio.eq(0) ? 0 : 100 / Number(issuanceRatio); + + const optimismDeadline = useLiquidationOptimismSubgraph(); + const deadline = + optimismDeadline > 0 + ? optimismDeadline + : Number(liquidationDeadlineForAccount.toString()) * 1000; + + const hasWarning = + optimismDeadline > 0 || (!liquidationDeadlineForAccount.eq(0) && cRatio.gt(issuanceRatio)); + + return { + hasWarning, + ratio, + deadline, + }; +} diff --git a/components/BannerManager/Liquidation/useLiquidationOptimismSubgraph.ts b/components/BannerManager/Liquidation/useLiquidationOptimismSubgraph.ts new file mode 100644 index 000000000..b05ff33d4 --- /dev/null +++ b/components/BannerManager/Liquidation/useLiquidationOptimismSubgraph.ts @@ -0,0 +1,81 @@ +import { useQuery, UseQueryOptions } from 'react-query'; +import Connector from 'containers/Connector'; + +function getEndpoint(networkName: String) { + switch (networkName) { + case 'kovan-ovm': + return 'https://api.thegraph.com/subgraphs/name/noisekit/liquidator-optimism-kovan'; + case 'mainnet-ovm': + return 'https://api.thegraph.com/subgraphs/name/noisekit/liquidator-optimism'; + default: + return null; + } +} + +const gql = (data: any) => data[0]; +const query = gql` + query stakerEntity($id: String!) { + stakerEntity(id: $id) { + id + timestamp + status + } + } +`; + +export async function fetchLiquidationInfo( + walletAddress: string | null, + networkName: string | undefined +) { + if (!walletAddress) { + return null; + } + if (!networkName) { + return null; + } + const endpoint = getEndpoint(networkName); + if (!endpoint) { + return null; + } + + const body = await fetch(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: JSON.stringify({ + query, + variables: { + id: walletAddress, + }, + }), + }); + + const { data, errors } = await body.json(); + if (errors?.[0]) { + throw new Error(errors?.[0]?.message || 'Unknown server error'); + } + return data?.stakerEntity; +} + +export function useLiquidationOptimismInfo(queryOptions?: UseQueryOptions) { + const { walletAddress, network } = Connector.useContainer(); + return useQuery( + [walletAddress, network?.name], + () => fetchLiquidationInfo(walletAddress, network?.name), + { + enabled: Boolean(walletAddress && network?.name), + ...queryOptions, + } + ); +} + +export function useLiquidationOptimismSubgraph(queryOptions?: UseQueryOptions) { + const { data, isSuccess } = useLiquidationOptimismInfo(queryOptions); + if (!isSuccess) { + return 0; + } + // @ts-ignore I give up + return data?.status === 'FLAGGED' ? parseInt(data.timestamp, 10) * 1000 : 0; +} From 268092615dcfd9e72ebdb2092f30746b48a85cda Mon Sep 17 00:00:00 2001 From: bachstatter Date: Mon, 1 Aug 2022 15:10:45 +0100 Subject: [PATCH 2/2] Example of how to type react query hooks --- .../useLiquidationOptimismSubgraph.ts | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/components/BannerManager/Liquidation/useLiquidationOptimismSubgraph.ts b/components/BannerManager/Liquidation/useLiquidationOptimismSubgraph.ts index b05ff33d4..15f69dd26 100644 --- a/components/BannerManager/Liquidation/useLiquidationOptimismSubgraph.ts +++ b/components/BannerManager/Liquidation/useLiquidationOptimismSubgraph.ts @@ -8,7 +8,7 @@ function getEndpoint(networkName: String) { case 'mainnet-ovm': return 'https://api.thegraph.com/subgraphs/name/noisekit/liquidator-optimism'; default: - return null; + throw Error(`Called with unsupported network: ${networkName}`); } } @@ -23,20 +23,14 @@ const query = gql` } `; -export async function fetchLiquidationInfo( - walletAddress: string | null, - networkName: string | undefined -) { - if (!walletAddress) { - return null; - } - if (!networkName) { - return null; - } +type StakerEntity = { + id: string; + timestamp: string; + status: string; +} | null; + +export async function fetchLiquidationInfo(walletAddress: string, networkName: string) { const endpoint = getEndpoint(networkName); - if (!endpoint) { - return null; - } const body = await fetch(endpoint, { method: 'POST', @@ -52,18 +46,30 @@ export async function fetchLiquidationInfo( }), }); - const { data, errors } = await body.json(); + const { + errors, + data, + }: { + errors?: Error[]; + data: { stakerEntity: StakerEntity }; + } = await body.json(); + if (errors?.[0]) { throw new Error(errors?.[0]?.message || 'Unknown server error'); } return data?.stakerEntity; } -export function useLiquidationOptimismInfo(queryOptions?: UseQueryOptions) { +export function useLiquidationOptimismInfo(queryOptions?: UseQueryOptions) { const { walletAddress, network } = Connector.useContainer(); return useQuery( [walletAddress, network?.name], - () => fetchLiquidationInfo(walletAddress, network?.name), + () => { + if (!walletAddress || !network?.name) { + throw Error('Missing address or network, query should not run without it'); + } + return fetchLiquidationInfo(walletAddress, network?.name); + }, { enabled: Boolean(walletAddress && network?.name), ...queryOptions, @@ -71,11 +77,10 @@ export function useLiquidationOptimismInfo(queryOptions?: UseQueryOptions) { ); } -export function useLiquidationOptimismSubgraph(queryOptions?: UseQueryOptions) { +export function useLiquidationOptimismSubgraph(queryOptions?: UseQueryOptions) { const { data, isSuccess } = useLiquidationOptimismInfo(queryOptions); if (!isSuccess) { return 0; } - // @ts-ignore I give up return data?.status === 'FLAGGED' ? parseInt(data.timestamp, 10) * 1000 : 0; }