Skip to content

Commit 254dc1a

Browse files
Merge pull request #490 from threshold-network/tbtc-stats-on-overview-page
tBTC stats on overview page Adds the tBTC stats to the overview page and tBTC bridge page as an empty state before the user connects with a wallet.
2 parents 69122d4 + 4324da8 commit 254dc1a

File tree

16 files changed

+499
-78
lines changed

16 files changed

+499
-78
lines changed

src/components/OutlineListItem.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { FC } from "react"
2+
import { ListItem, ListItemProps } from "@threshold-network/components"
3+
4+
export const OutlineListItem: FC<ListItemProps> = ({ ...props }) => {
5+
return (
6+
<ListItem
7+
display="flex"
8+
justifyContent="space-between"
9+
alignItems="center"
10+
borderColor="gray.100"
11+
borderWidth="1px"
12+
borderStyle="solid"
13+
borderRadius="6px"
14+
py="4"
15+
px="6"
16+
{...props}
17+
/>
18+
)
19+
}

src/components/ViewInBlockExplorer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { supportedChainId } from "../utils/getEnvVariable"
99

1010
export type Chain = "bitcoin" | "ethereum"
1111

12-
const createLinkToBlockExplorerForChain: Record<
12+
export const createLinkToBlockExplorerForChain: Record<
1313
Chain,
1414
(id: string, type: ExplorerDataType) => string
1515
> = {
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { FC } from "react"
2+
import {
3+
BodySm,
4+
List,
5+
HStack,
6+
ListProps,
7+
LinkBox,
8+
LinkOverlay,
9+
Link,
10+
} from "@threshold-network/components"
11+
import shortenAddress from "../../utils/shortenAddress"
12+
import Identicon from "../Identicon"
13+
import { OutlineListItem } from "../OutlineListItem"
14+
import { InlineTokenBalance } from "../TokenBalance"
15+
import { getRelativeTime } from "../../utils/date"
16+
import { RecentDeposit } from "../../hooks/tbtc/useFetchRecentDeposits"
17+
import ViewInBlockExplorer, {
18+
createLinkToBlockExplorerForChain,
19+
} from "../ViewInBlockExplorer"
20+
import { ExplorerDataType } from "../../utils/createEtherscanLink"
21+
22+
export type RecentDepositsProps = {
23+
deposits: RecentDeposit[]
24+
} & ListProps
25+
26+
export const RecentDeposits: FC<RecentDepositsProps> = ({
27+
deposits,
28+
...restProps
29+
}) => {
30+
return (
31+
<List spacing="1" position="relative" {...restProps}>
32+
{deposits.map(renderRecentDeposit)}
33+
</List>
34+
)
35+
}
36+
37+
const RecentDepositItem: FC<RecentDeposit> = ({
38+
amount,
39+
address,
40+
date,
41+
txHash,
42+
}) => {
43+
return (
44+
<LinkBox as={OutlineListItem}>
45+
<LinkOverlay
46+
// We can't use `ViewInBlockExplorer` or our custom `Link` component
47+
// because `LinkOverlay` component from chakra doesn't pass the
48+
// `isExternal` prop forward so `ViewInBlockExplorer` or `Link`
49+
// component sees this link as an internal so the link will open in the
50+
// same tab and the TS compiler throws an error that `to` prop is
51+
// missing because of conditional props of `Link` component.
52+
as={Link}
53+
textDecoration="none"
54+
_hover={{ textDecoration: "none" }}
55+
color="inherit"
56+
isExternal
57+
href={createLinkToBlockExplorerForChain.ethereum(
58+
txHash,
59+
ExplorerDataType.TRANSACTION
60+
)}
61+
>
62+
<BodySm>
63+
<InlineTokenBalance
64+
tokenAmount={amount}
65+
withSymbol
66+
tokenSymbol="tBTC"
67+
color="brand.500"
68+
/>
69+
</BodySm>
70+
</LinkOverlay>
71+
<HStack spacing="2">
72+
<Identicon address={address} />
73+
<BodySm textStyle="chain-identifier">{shortenAddress(address)}</BodySm>
74+
</HStack>
75+
<BodySm>{getRelativeTime(date)}</BodySm>
76+
</LinkBox>
77+
)
78+
}
79+
80+
const renderRecentDeposit = (item: RecentDeposit) => (
81+
<RecentDepositItem key={item.txHash} {...item} />
82+
)

src/components/tBTC/Stats.tsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { ComponentProps, FC } from "react"
2+
import { BodyMd, Box, BoxProps, H1, H5 } from "@threshold-network/components"
3+
import { InlineTokenBalance } from "../TokenBalance"
4+
import { formatFiatCurrencyAmount } from "../../utils/formatAmount"
5+
import { RecentDeposits, RecentDepositsProps } from "./RecentDeposits"
6+
import { ExternalHref } from "../../enums"
7+
import { RecentDeposit } from "../../hooks/tbtc"
8+
import Link from "../Link"
9+
10+
export type TVLProps = {
11+
tvl: string
12+
tvlInUSD: string
13+
}
14+
15+
export const TVL: FC<TVLProps> = ({ tvl, tvlInUSD }) => {
16+
return (
17+
<>
18+
<BodyMd mb="1.5">TVL</BodyMd>
19+
<H1 textAlign="center">
20+
<InlineTokenBalance tokenAmount={tvl} /> <H5 as="span">tBTC</H5>
21+
</H1>
22+
<BodyMd textAlign="center">
23+
{formatFiatCurrencyAmount(tvlInUSD, "0,00.00")} USD
24+
</BodyMd>
25+
</>
26+
)
27+
}
28+
29+
export type ProtocolHistoryProps = {
30+
deposits: RecentDeposit[]
31+
}
32+
33+
export const ProtocolHistoryTitle: FC<ComponentProps<typeof BodyMd>> = (
34+
props
35+
) => (
36+
<BodyMd mb="3" {...props}>
37+
Protocol History
38+
</BodyMd>
39+
)
40+
41+
export const ProtocolHistoryViewMoreLink: FC<BoxProps> = (props) => (
42+
<Box as="p" mt="3.5" textAlign="center" {...props}>
43+
<Link isExternal href={ExternalHref.tBTCDuneDashboard}>
44+
View on Dune Analytics
45+
</Link>
46+
</Box>
47+
)
48+
49+
export const ProtocolHistoryRecentDeposits: FC<RecentDepositsProps> = ({
50+
deposits,
51+
...restProps
52+
}) => <RecentDeposits deposits={deposits} {...restProps} />
53+
54+
export const ProtocolHistory: FC<ProtocolHistoryProps> = ({ deposits }) => {
55+
return (
56+
<>
57+
<ProtocolHistoryTitle />
58+
<ProtocolHistoryRecentDeposits deposits={deposits} />
59+
<ProtocolHistoryViewMoreLink />
60+
</>
61+
)
62+
}

src/components/tBTC/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from "./TakeNoteList"
22
export * from "./Links"
3+
export * from "./Stats"

src/enums/externalHref.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,5 @@ export enum ExternalHref {
3232
tBTCRecoveryGuide = "https://github.com/keep-network/tbtc-v2/blob/main/typescript/scripts/README.adoc",
3333
btcConfirmations = "https://en.bitcoin.it/wiki/Confirmation",
3434
mintersAndGuardiansDocs = "https://blog.threshold.network/minters-guardians-and-a-strong-tbtc/",
35+
tBTCDuneDashboard = "https://dune.com/threshold/tbtc",
3536
}

src/hooks/__tests__/useFetchTvl.test.tsx

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { useFetchTvl } from "../useFetchTvl"
1414
import * as useTokenModule from "../useToken"
1515
import { TokenContext } from "../../contexts/TokenContext"
1616
import * as usdUtils from "../../utils/getUsdBalance"
17+
import { useTBTCv2TokenContract } from "../../web3/hooks/useTBTCv2TokenContract"
1718

1819
jest.mock("../../web3/hooks", () => ({
1920
...(jest.requireActual("../../web3/hooks") as {}),
@@ -25,6 +26,11 @@ jest.mock("../../web3/hooks", () => ({
2526
useTStakingContract: jest.fn(),
2627
}))
2728

29+
jest.mock("../../web3/hooks/useTBTCv2TokenContract", () => ({
30+
...(jest.requireActual("../../web3/hooks/useTBTCv2TokenContract") as {}),
31+
useTBTCv2TokenContract: jest.fn(),
32+
}))
33+
2834
jest.mock("../useETHData", () => ({
2935
...(jest.requireActual("../useETHData") as {}),
3036
useETHData: jest.fn(),
@@ -35,12 +41,7 @@ describe("Test `useFetchTvl` hook", () => {
3541
contract: {} as any,
3642
usdConversion: 1,
3743
} as any
38-
const tbtcContext = {
39-
contract: {} as any,
40-
usdConversion: 2,
41-
} as any
42-
43-
const tbtcv2Context = {
44+
const tbtcv1Context = {
4445
contract: {} as any,
4546
usdConversion: 2,
4647
} as any
@@ -54,21 +55,25 @@ describe("Test `useFetchTvl` hook", () => {
5455
contract: {} as any,
5556
usdConversion: 4,
5657
} as any
57-
58+
const tBTCContext = {
59+
contract: {} as any,
60+
usdConversion: 2,
61+
} as any
5862
const mockedKeepTokenStakingContract = { address: "0x1" }
5963
const mockedKeepBondingContract = { address: "0x0" }
6064
const mockedTStakingContract = { address: "0x2" }
6165
const mockedMultiCallContract = { interface: {}, address: "0x3" }
6266
const mockedKeepAssetPoolContract = { interface: {}, address: "0x4" }
67+
const mockedtBTCTokenContract = { interface: {}, address: "0x5" }
6368

6469
const wrapper = ({ children }) => (
6570
<TokenContext.Provider
6671
value={{
6772
[Token.Keep]: keepContext,
68-
[Token.TBTC]: tbtcContext,
69-
[Token.TBTCV2]: tbtcv2Context,
73+
[Token.TBTC]: tbtcv1Context,
7074
[Token.T]: tContext,
7175
[Token.Nu]: nuContext,
76+
[Token.TBTCV2]: tBTCContext,
7277
}}
7378
>
7479
{children}
@@ -94,6 +99,9 @@ describe("Test `useFetchTvl` hook", () => {
9499
;(useKeepTokenStakingContract as jest.Mock).mockReturnValue(
95100
mockedKeepTokenStakingContract
96101
)
102+
;(useTBTCv2TokenContract as jest.Mock).mockReturnValue(
103+
mockedtBTCTokenContract
104+
)
97105
})
98106

99107
test("should fetch tvl data correctly.", async () => {
@@ -103,13 +111,15 @@ describe("Test `useFetchTvl` hook", () => {
103111
const coveragePoolTvl = { raw: "300000000000000000000", format: "300.0" }
104112
const keepStaking = { raw: "500000000000000000000", format: "500.0" }
105113
const tStaking = { raw: "600000000000000000000", format: "600.0" }
114+
const tBTC = { raw: "700000000000000000000", format: "700.0" }
106115

107116
const multicallRequestResult = [
108117
ethInKeepBonding.raw,
109118
tbtcTokenTotalSupply.raw,
110119
coveragePoolTvl.raw,
111120
keepStaking.raw,
112121
tStaking.raw,
122+
tBTC.raw,
113123
]
114124

115125
multicallRequest.mockResolvedValue(multicallRequestResult)
@@ -119,27 +129,30 @@ describe("Test `useFetchTvl` hook", () => {
119129
const spyOnUseToken = jest.spyOn(useTokenModule, "useToken")
120130

121131
const _expectedResult = {
122-
ecdsa: ethInKeepBonding.format * mockedETHData.usdPrice,
123-
tbtc: tbtcTokenTotalSupply.format * tbtcContext.usdConversion,
124-
keepCoveragePool: coveragePoolTvl.format * keepContext.usdConversion,
125-
keepStaking: keepStaking.format * keepContext.usdConversion,
126-
tStaking: tStaking.format * tContext.usdConversion,
132+
ecdsa: +ethInKeepBonding.format * mockedETHData.usdPrice,
133+
tbtc: +tbtcTokenTotalSupply.format * tbtcv1Context.usdConversion,
134+
keepCoveragePool: +coveragePoolTvl.format * keepContext.usdConversion,
135+
keepStaking: +keepStaking.format * keepContext.usdConversion,
136+
tStaking: +tStaking.format * tContext.usdConversion,
137+
tBTC: +tBTC.format * tBTCContext.usdConversion,
127138
}
128139

129140
// `FixedNumber` from `@ethersproject/bignumber` adds trailing zero so we
130141
// need to do the same here.
131142
const expectedResult = {
132143
ecdsa: `${_expectedResult.ecdsa.toString()}.0`,
133-
tbtc: `${_expectedResult.tbtc.toString()}.0`,
144+
tbtcv1: `${_expectedResult.tbtc.toString()}.0`,
134145
keepCoveragePool: `${_expectedResult.keepCoveragePool.toString()}.0`,
135146
keepStaking: `${_expectedResult.keepStaking.toString()}.0`,
136147
tStaking: `${_expectedResult.tStaking.toString()}.0`,
148+
tBTC: `${_expectedResult.tBTC.toString()}.0`,
137149
total: `${
138150
_expectedResult.ecdsa +
139151
_expectedResult.tbtc +
140152
_expectedResult.keepCoveragePool +
141153
_expectedResult.keepStaking +
142-
_expectedResult.tStaking
154+
_expectedResult.tStaking +
155+
_expectedResult.tBTC
143156
}.0`,
144157
}
145158

@@ -153,6 +166,7 @@ describe("Test `useFetchTvl` hook", () => {
153166
expect(spyOnUseToken).toHaveBeenCalledWith(Token.Keep)
154167
expect(spyOnUseToken).toHaveBeenCalledWith(Token.TBTC)
155168
expect(spyOnUseToken).toHaveBeenCalledWith(Token.T)
169+
expect(spyOnUseToken).toHaveBeenCalledWith(Token.TBTCV2)
156170
expect(useKeepBondingContract).toHaveBeenCalled()
157171
expect(useMulticallContract).toHaveBeenCalled()
158172
expect(useKeepAssetPoolContract).toHaveBeenCalled()
@@ -166,8 +180,8 @@ describe("Test `useFetchTvl` hook", () => {
166180
args: [mockedKeepBondingContract.address],
167181
},
168182
{
169-
interface: tbtcContext.contract.interface,
170-
address: tbtcContext.contract.address,
183+
interface: tbtcv1Context.contract.interface,
184+
address: tbtcv1Context.contract.address,
171185
method: "totalSupply",
172186
},
173187
{
@@ -187,43 +201,56 @@ describe("Test `useFetchTvl` hook", () => {
187201
method: "balanceOf",
188202
args: [mockedTStakingContract.address],
189203
},
204+
{
205+
interface: tBTCContext.contract.interface,
206+
address: tBTCContext.contract.address,
207+
method: "totalSupply",
208+
},
190209
])
191210

192211
result.current[1]()
193212

194213
await waitForNextUpdate()
195214

196215
expect(multicallRequest).toHaveBeenCalled()
197-
expect(spyOnFormatUnits).toHaveBeenCalledTimes(
198-
multicallRequestResult.length
199-
)
200-
// The `toUsdBalance` function was called 2x times because it was called
201-
// first on mount for every value and then after fetching on-chain data.
216+
217+
// The `toUsdBalance` and `spyOnFormatUnits` function were called 2x times
218+
// because they were called called first on mount for every value and then
219+
// after fetching on-chain data.
202220
expect(spyOnToUsdBalance).toHaveBeenCalledTimes(
203221
multicallRequestResult.length * 2
204222
)
223+
expect(spyOnFormatUnits).toHaveBeenCalledTimes(
224+
multicallRequestResult.length * 2
225+
)
226+
205227
expect(spyOnToUsdBalance).toHaveBeenNthCalledWith(
206-
6,
228+
7,
207229
ethInKeepBonding.format,
208230
mockedETHData.usdPrice
209231
)
210232
expect(spyOnToUsdBalance).toHaveBeenNthCalledWith(
211-
7,
233+
8,
212234
tbtcTokenTotalSupply.format,
213-
tbtcContext.usdConversion
235+
tbtcv1Context.usdConversion
214236
)
215237
expect(spyOnToUsdBalance).toHaveBeenNthCalledWith(
216-
8,
238+
9,
239+
tBTC.format,
240+
tBTCContext.usdConversion
241+
)
242+
expect(spyOnToUsdBalance).toHaveBeenNthCalledWith(
243+
10,
217244
coveragePoolTvl.format,
218245
keepContext.usdConversion
219246
)
220247
expect(spyOnToUsdBalance).toHaveBeenNthCalledWith(
221-
9,
248+
11,
222249
keepStaking.format,
223250
keepContext.usdConversion
224251
)
225252
expect(spyOnToUsdBalance).toHaveBeenNthCalledWith(
226-
10,
253+
12,
227254
tStaking.format,
228255
tContext.usdConversion
229256
)

0 commit comments

Comments
 (0)