-
Notifications
You must be signed in to change notification settings - Fork 26
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
tBTC stats on overview page #490
Changes from all commits
879bf68
c49e4ff
f052046
93933be
9872656
1dfd447
060f20e
fdb995a
721c248
abdaa98
64fbb14
e794f29
197bd1d
713bf2f
c0b941f
4324da8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { FC } from "react" | ||
import { ListItem, ListItemProps } from "@threshold-network/components" | ||
|
||
export const OutlineListItem: FC<ListItemProps> = ({ ...props }) => { | ||
return ( | ||
<ListItem | ||
display="flex" | ||
justifyContent="space-between" | ||
alignItems="center" | ||
borderColor="gray.100" | ||
borderWidth="1px" | ||
borderStyle="solid" | ||
borderRadius="6px" | ||
py="4" | ||
px="6" | ||
{...props} | ||
/> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import { FC } from "react" | ||
import { | ||
BodySm, | ||
List, | ||
HStack, | ||
ListProps, | ||
LinkBox, | ||
LinkOverlay, | ||
Link, | ||
} from "@threshold-network/components" | ||
import shortenAddress from "../../utils/shortenAddress" | ||
import Identicon from "../Identicon" | ||
import { OutlineListItem } from "../OutlineListItem" | ||
import { InlineTokenBalance } from "../TokenBalance" | ||
import { getRelativeTime } from "../../utils/date" | ||
import { RecentDeposit } from "../../hooks/tbtc/useFetchRecentDeposits" | ||
import ViewInBlockExplorer, { | ||
createLinkToBlockExplorerForChain, | ||
} from "../ViewInBlockExplorer" | ||
import { ExplorerDataType } from "../../utils/createEtherscanLink" | ||
|
||
export type RecentDepositsProps = { | ||
deposits: RecentDeposit[] | ||
} & ListProps | ||
|
||
export const RecentDeposits: FC<RecentDepositsProps> = ({ | ||
deposits, | ||
...restProps | ||
}) => { | ||
return ( | ||
<List spacing="1" position="relative" {...restProps}> | ||
{deposits.map(renderRecentDeposit)} | ||
</List> | ||
) | ||
} | ||
|
||
const RecentDepositItem: FC<RecentDeposit> = ({ | ||
amount, | ||
address, | ||
date, | ||
txHash, | ||
}) => { | ||
return ( | ||
<LinkBox as={OutlineListItem}> | ||
<LinkOverlay | ||
// We can't use `ViewInBlockExplorer` or our custom `Link` component | ||
// because `LinkOverlay` component from chakra doesn't pass the | ||
// `isExternal` prop forward so `ViewInBlockExplorer` or `Link` | ||
// component sees this link as an internal so the link will open in the | ||
// same tab and the TS compiler throws an error that `to` prop is | ||
// missing because of conditional props of `Link` component. | ||
as={Link} | ||
textDecoration="none" | ||
_hover={{ textDecoration: "none" }} | ||
color="inherit" | ||
isExternal | ||
href={createLinkToBlockExplorerForChain.ethereum( | ||
txHash, | ||
ExplorerDataType.TRANSACTION | ||
)} | ||
> | ||
<BodySm> | ||
<InlineTokenBalance | ||
tokenAmount={amount} | ||
withSymbol | ||
tokenSymbol="tBTC" | ||
color="brand.500" | ||
/> | ||
</BodySm> | ||
</LinkOverlay> | ||
<HStack spacing="2"> | ||
<Identicon address={address} /> | ||
<BodySm textStyle="chain-identifier">{shortenAddress(address)}</BodySm> | ||
</HStack> | ||
<BodySm>{getRelativeTime(date)}</BodySm> | ||
</LinkBox> | ||
) | ||
} | ||
|
||
const renderRecentDeposit = (item: RecentDeposit) => ( | ||
<RecentDepositItem key={item.txHash} {...item} /> | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { ComponentProps, FC } from "react" | ||
import { BodyMd, Box, BoxProps, H1, H5 } from "@threshold-network/components" | ||
import { InlineTokenBalance } from "../TokenBalance" | ||
import { formatFiatCurrencyAmount } from "../../utils/formatAmount" | ||
import { RecentDeposits, RecentDepositsProps } from "./RecentDeposits" | ||
import { ExternalHref } from "../../enums" | ||
import { RecentDeposit } from "../../hooks/tbtc" | ||
import Link from "../Link" | ||
|
||
export type TVLProps = { | ||
tvl: string | ||
tvlInUSD: string | ||
} | ||
|
||
export const TVL: FC<TVLProps> = ({ tvl, tvlInUSD }) => { | ||
return ( | ||
<> | ||
<BodyMd mb="1.5">TVL</BodyMd> | ||
<H1 textAlign="center"> | ||
<InlineTokenBalance tokenAmount={tvl} /> <H5 as="span">tBTC</H5> | ||
</H1> | ||
<BodyMd textAlign="center"> | ||
{formatFiatCurrencyAmount(tvlInUSD, "0,00.00")} USD | ||
</BodyMd> | ||
</> | ||
) | ||
} | ||
|
||
export type ProtocolHistoryProps = { | ||
deposits: RecentDeposit[] | ||
} | ||
|
||
export const ProtocolHistoryTitle: FC<ComponentProps<typeof BodyMd>> = ( | ||
props | ||
) => ( | ||
<BodyMd mb="3" {...props}> | ||
Protocol History | ||
</BodyMd> | ||
) | ||
|
||
export const ProtocolHistoryViewMoreLink: FC<BoxProps> = (props) => ( | ||
<Box as="p" mt="3.5" textAlign="center" {...props}> | ||
<Link isExternal href={ExternalHref.tBTCDuneDashboard}> | ||
View on Dune Analytics | ||
</Link> | ||
</Box> | ||
) | ||
|
||
export const ProtocolHistoryRecentDeposits: FC<RecentDepositsProps> = ({ | ||
deposits, | ||
...restProps | ||
}) => <RecentDeposits deposits={deposits} {...restProps} /> | ||
|
||
export const ProtocolHistory: FC<ProtocolHistoryProps> = ({ deposits }) => { | ||
return ( | ||
<> | ||
<ProtocolHistoryTitle /> | ||
<ProtocolHistoryRecentDeposits deposits={deposits} /> | ||
<ProtocolHistoryViewMoreLink /> | ||
</> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from "./TakeNoteList" | ||
export * from "./Links" | ||
export * from "./Stats" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ import { useFetchTvl } from "../useFetchTvl" | |
import * as useTokenModule from "../useToken" | ||
import { TokenContext } from "../../contexts/TokenContext" | ||
import * as usdUtils from "../../utils/getUsdBalance" | ||
import { useTBTCv2TokenContract } from "../../web3/hooks/useTBTCv2TokenContract" | ||
|
||
jest.mock("../../web3/hooks", () => ({ | ||
...(jest.requireActual("../../web3/hooks") as {}), | ||
|
@@ -25,6 +26,11 @@ jest.mock("../../web3/hooks", () => ({ | |
useTStakingContract: jest.fn(), | ||
})) | ||
|
||
jest.mock("../../web3/hooks/useTBTCv2TokenContract", () => ({ | ||
...(jest.requireActual("../../web3/hooks/useTBTCv2TokenContract") as {}), | ||
useTBTCv2TokenContract: jest.fn(), | ||
})) | ||
|
||
jest.mock("../useETHData", () => ({ | ||
...(jest.requireActual("../useETHData") as {}), | ||
useETHData: jest.fn(), | ||
|
@@ -35,12 +41,7 @@ describe("Test `useFetchTvl` hook", () => { | |
contract: {} as any, | ||
usdConversion: 1, | ||
} as any | ||
const tbtcContext = { | ||
contract: {} as any, | ||
usdConversion: 2, | ||
} as any | ||
|
||
const tbtcv2Context = { | ||
const tbtcv1Context = { | ||
contract: {} as any, | ||
usdConversion: 2, | ||
} as any | ||
|
@@ -54,21 +55,25 @@ describe("Test `useFetchTvl` hook", () => { | |
contract: {} as any, | ||
usdConversion: 4, | ||
} as any | ||
|
||
const tBTCContext = { | ||
contract: {} as any, | ||
usdConversion: 2, | ||
} as any | ||
const mockedKeepTokenStakingContract = { address: "0x1" } | ||
const mockedKeepBondingContract = { address: "0x0" } | ||
const mockedTStakingContract = { address: "0x2" } | ||
const mockedMultiCallContract = { interface: {}, address: "0x3" } | ||
const mockedKeepAssetPoolContract = { interface: {}, address: "0x4" } | ||
const mockedtBTCTokenContract = { interface: {}, address: "0x5" } | ||
|
||
const wrapper = ({ children }) => ( | ||
<TokenContext.Provider | ||
value={{ | ||
[Token.Keep]: keepContext, | ||
[Token.TBTC]: tbtcContext, | ||
[Token.TBTCV2]: tbtcv2Context, | ||
[Token.TBTC]: tbtcv1Context, | ||
[Token.T]: tContext, | ||
[Token.Nu]: nuContext, | ||
[Token.TBTCV2]: tBTCContext, | ||
Comment on lines
+73
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-blocking: I think we should decide which naming convention we want to use for both tbtc tokens and stick to use. We can either use [ We don't have to do it in this PR though. It's just something that will have to be resolved sooner or later and the code needs to be checked for those occurences. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
}} | ||
> | ||
{children} | ||
|
@@ -94,6 +99,9 @@ describe("Test `useFetchTvl` hook", () => { | |
;(useKeepTokenStakingContract as jest.Mock).mockReturnValue( | ||
mockedKeepTokenStakingContract | ||
) | ||
;(useTBTCv2TokenContract as jest.Mock).mockReturnValue( | ||
mockedtBTCTokenContract | ||
) | ||
}) | ||
|
||
test("should fetch tvl data correctly.", async () => { | ||
|
@@ -103,13 +111,15 @@ describe("Test `useFetchTvl` hook", () => { | |
const coveragePoolTvl = { raw: "300000000000000000000", format: "300.0" } | ||
const keepStaking = { raw: "500000000000000000000", format: "500.0" } | ||
const tStaking = { raw: "600000000000000000000", format: "600.0" } | ||
const tBTC = { raw: "700000000000000000000", format: "700.0" } | ||
|
||
const multicallRequestResult = [ | ||
ethInKeepBonding.raw, | ||
tbtcTokenTotalSupply.raw, | ||
coveragePoolTvl.raw, | ||
keepStaking.raw, | ||
tStaking.raw, | ||
tBTC.raw, | ||
] | ||
|
||
multicallRequest.mockResolvedValue(multicallRequestResult) | ||
|
@@ -119,27 +129,30 @@ describe("Test `useFetchTvl` hook", () => { | |
const spyOnUseToken = jest.spyOn(useTokenModule, "useToken") | ||
|
||
const _expectedResult = { | ||
ecdsa: ethInKeepBonding.format * mockedETHData.usdPrice, | ||
tbtc: tbtcTokenTotalSupply.format * tbtcContext.usdConversion, | ||
keepCoveragePool: coveragePoolTvl.format * keepContext.usdConversion, | ||
keepStaking: keepStaking.format * keepContext.usdConversion, | ||
tStaking: tStaking.format * tContext.usdConversion, | ||
ecdsa: +ethInKeepBonding.format * mockedETHData.usdPrice, | ||
tbtc: +tbtcTokenTotalSupply.format * tbtcv1Context.usdConversion, | ||
keepCoveragePool: +coveragePoolTvl.format * keepContext.usdConversion, | ||
keepStaking: +keepStaking.format * keepContext.usdConversion, | ||
tStaking: +tStaking.format * tContext.usdConversion, | ||
tBTC: +tBTC.format * tBTCContext.usdConversion, | ||
} | ||
|
||
// `FixedNumber` from `@ethersproject/bignumber` adds trailing zero so we | ||
// need to do the same here. | ||
const expectedResult = { | ||
ecdsa: `${_expectedResult.ecdsa.toString()}.0`, | ||
tbtc: `${_expectedResult.tbtc.toString()}.0`, | ||
tbtcv1: `${_expectedResult.tbtc.toString()}.0`, | ||
keepCoveragePool: `${_expectedResult.keepCoveragePool.toString()}.0`, | ||
keepStaking: `${_expectedResult.keepStaking.toString()}.0`, | ||
tStaking: `${_expectedResult.tStaking.toString()}.0`, | ||
tBTC: `${_expectedResult.tBTC.toString()}.0`, | ||
total: `${ | ||
_expectedResult.ecdsa + | ||
_expectedResult.tbtc + | ||
_expectedResult.keepCoveragePool + | ||
_expectedResult.keepStaking + | ||
_expectedResult.tStaking | ||
_expectedResult.tStaking + | ||
_expectedResult.tBTC | ||
}.0`, | ||
} | ||
|
||
|
@@ -153,6 +166,7 @@ describe("Test `useFetchTvl` hook", () => { | |
expect(spyOnUseToken).toHaveBeenCalledWith(Token.Keep) | ||
expect(spyOnUseToken).toHaveBeenCalledWith(Token.TBTC) | ||
expect(spyOnUseToken).toHaveBeenCalledWith(Token.T) | ||
expect(spyOnUseToken).toHaveBeenCalledWith(Token.TBTCV2) | ||
expect(useKeepBondingContract).toHaveBeenCalled() | ||
expect(useMulticallContract).toHaveBeenCalled() | ||
expect(useKeepAssetPoolContract).toHaveBeenCalled() | ||
|
@@ -166,8 +180,8 @@ describe("Test `useFetchTvl` hook", () => { | |
args: [mockedKeepBondingContract.address], | ||
}, | ||
{ | ||
interface: tbtcContext.contract.interface, | ||
address: tbtcContext.contract.address, | ||
interface: tbtcv1Context.contract.interface, | ||
address: tbtcv1Context.contract.address, | ||
method: "totalSupply", | ||
}, | ||
{ | ||
|
@@ -187,43 +201,56 @@ describe("Test `useFetchTvl` hook", () => { | |
method: "balanceOf", | ||
args: [mockedTStakingContract.address], | ||
}, | ||
{ | ||
interface: tBTCContext.contract.interface, | ||
address: tBTCContext.contract.address, | ||
method: "totalSupply", | ||
}, | ||
]) | ||
|
||
result.current[1]() | ||
|
||
await waitForNextUpdate() | ||
|
||
expect(multicallRequest).toHaveBeenCalled() | ||
expect(spyOnFormatUnits).toHaveBeenCalledTimes( | ||
multicallRequestResult.length | ||
) | ||
// The `toUsdBalance` function was called 2x times because it was called | ||
// first on mount for every value and then after fetching on-chain data. | ||
|
||
// The `toUsdBalance` and `spyOnFormatUnits` function were called 2x times | ||
// because they were called called first on mount for every value and then | ||
// after fetching on-chain data. | ||
expect(spyOnToUsdBalance).toHaveBeenCalledTimes( | ||
multicallRequestResult.length * 2 | ||
) | ||
expect(spyOnFormatUnits).toHaveBeenCalledTimes( | ||
multicallRequestResult.length * 2 | ||
) | ||
|
||
expect(spyOnToUsdBalance).toHaveBeenNthCalledWith( | ||
6, | ||
7, | ||
ethInKeepBonding.format, | ||
mockedETHData.usdPrice | ||
) | ||
expect(spyOnToUsdBalance).toHaveBeenNthCalledWith( | ||
7, | ||
8, | ||
tbtcTokenTotalSupply.format, | ||
tbtcContext.usdConversion | ||
tbtcv1Context.usdConversion | ||
) | ||
expect(spyOnToUsdBalance).toHaveBeenNthCalledWith( | ||
8, | ||
9, | ||
tBTC.format, | ||
tBTCContext.usdConversion | ||
) | ||
expect(spyOnToUsdBalance).toHaveBeenNthCalledWith( | ||
10, | ||
coveragePoolTvl.format, | ||
keepContext.usdConversion | ||
) | ||
expect(spyOnToUsdBalance).toHaveBeenNthCalledWith( | ||
9, | ||
11, | ||
keepStaking.format, | ||
keepContext.usdConversion | ||
) | ||
expect(spyOnToUsdBalance).toHaveBeenNthCalledWith( | ||
10, | ||
12, | ||
tStaking.format, | ||
tContext.usdConversion | ||
) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you double-check if all the tests pass? For me they don't (on your branch) but on
main
all have passedThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once we merge
main
should be all good 🤞