diff --git a/src/components/OutlineListItem.tsx b/src/components/OutlineListItem.tsx new file mode 100644 index 000000000..32e0f6812 --- /dev/null +++ b/src/components/OutlineListItem.tsx @@ -0,0 +1,19 @@ +import { FC } from "react" +import { ListItem, ListItemProps } from "@threshold-network/components" + +export const OutlineListItem: FC = ({ ...props }) => { + return ( + + ) +} diff --git a/src/components/ViewInBlockExplorer.tsx b/src/components/ViewInBlockExplorer.tsx index 2649da6f5..8a149eb70 100644 --- a/src/components/ViewInBlockExplorer.tsx +++ b/src/components/ViewInBlockExplorer.tsx @@ -9,7 +9,7 @@ import { supportedChainId } from "../utils/getEnvVariable" export type Chain = "bitcoin" | "ethereum" -const createLinkToBlockExplorerForChain: Record< +export const createLinkToBlockExplorerForChain: Record< Chain, (id: string, type: ExplorerDataType) => string > = { diff --git a/src/components/tBTC/RecentDeposits.tsx b/src/components/tBTC/RecentDeposits.tsx new file mode 100644 index 000000000..e9f2be83d --- /dev/null +++ b/src/components/tBTC/RecentDeposits.tsx @@ -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 = ({ + deposits, + ...restProps +}) => { + return ( + + {deposits.map(renderRecentDeposit)} + + ) +} + +const RecentDepositItem: FC = ({ + amount, + address, + date, + txHash, +}) => { + return ( + + + + + + + + + {shortenAddress(address)} + + {getRelativeTime(date)} + + ) +} + +const renderRecentDeposit = (item: RecentDeposit) => ( + +) diff --git a/src/components/tBTC/Stats.tsx b/src/components/tBTC/Stats.tsx new file mode 100644 index 000000000..5501d0e36 --- /dev/null +++ b/src/components/tBTC/Stats.tsx @@ -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 = ({ tvl, tvlInUSD }) => { + return ( + <> + TVL +

+
tBTC
+

+ + {formatFiatCurrencyAmount(tvlInUSD, "0,00.00")} USD + + + ) +} + +export type ProtocolHistoryProps = { + deposits: RecentDeposit[] +} + +export const ProtocolHistoryTitle: FC> = ( + props +) => ( + + Protocol History + +) + +export const ProtocolHistoryViewMoreLink: FC = (props) => ( + + + View on Dune Analytics + + +) + +export const ProtocolHistoryRecentDeposits: FC = ({ + deposits, + ...restProps +}) => + +export const ProtocolHistory: FC = ({ deposits }) => { + return ( + <> + + + + + ) +} diff --git a/src/components/tBTC/index.ts b/src/components/tBTC/index.ts index be61e49b7..564c7c00d 100644 --- a/src/components/tBTC/index.ts +++ b/src/components/tBTC/index.ts @@ -1,2 +1,3 @@ export * from "./TakeNoteList" export * from "./Links" +export * from "./Stats" diff --git a/src/enums/externalHref.ts b/src/enums/externalHref.ts index 9e8547241..7083ed670 100644 --- a/src/enums/externalHref.ts +++ b/src/enums/externalHref.ts @@ -32,4 +32,5 @@ export enum ExternalHref { tBTCRecoveryGuide = "https://github.com/keep-network/tbtc-v2/blob/main/typescript/scripts/README.adoc", btcConfirmations = "https://en.bitcoin.it/wiki/Confirmation", mintersAndGuardiansDocs = "https://blog.threshold.network/minters-guardians-and-a-strong-tbtc/", + tBTCDuneDashboard = "https://dune.com/threshold/tbtc", } diff --git a/src/hooks/__tests__/useFetchTvl.test.tsx b/src/hooks/__tests__/useFetchTvl.test.tsx index ad54c1626..7b0444fba 100644 --- a/src/hooks/__tests__/useFetchTvl.test.tsx +++ b/src/hooks/__tests__/useFetchTvl.test.tsx @@ -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 }) => ( {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,6 +111,7 @@ 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, @@ -110,6 +119,7 @@ describe("Test `useFetchTvl` hook", () => { 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,6 +201,11 @@ describe("Test `useFetchTvl` hook", () => { method: "balanceOf", args: [mockedTStakingContract.address], }, + { + interface: tBTCContext.contract.interface, + address: tBTCContext.contract.address, + method: "totalSupply", + }, ]) result.current[1]() @@ -194,36 +213,44 @@ describe("Test `useFetchTvl` hook", () => { 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 ) diff --git a/src/hooks/tbtc/index.ts b/src/hooks/tbtc/index.ts index 87ac00cc7..734d0c1e3 100644 --- a/src/hooks/tbtc/index.ts +++ b/src/hooks/tbtc/index.ts @@ -5,3 +5,4 @@ export * from "./useSubscribeToOptimisticMintingFinalizedEvent" export * from "./useSubsribeToDepositRevealedEvent" export * from "./useSubscribeToOptimisticMintingRequestedEvent" export * from "./useFetchDepositDetails" +export * from "./useFetchRecentDeposits" diff --git a/src/hooks/tbtc/useFetchRecentDeposits.ts b/src/hooks/tbtc/useFetchRecentDeposits.ts new file mode 100644 index 000000000..13360dfb9 --- /dev/null +++ b/src/hooks/tbtc/useFetchRecentDeposits.ts @@ -0,0 +1,55 @@ +import { useEffect, useRef, useState } from "react" +import { subgraphAPI } from "../../utils/subgraphAPI" + +export type RecentDeposit = { + amount: string + address: string + date: number + txHash: string +} + +export const useFetchRecentDeposits = ( + numberOfDeposits: number = 4 +): [RecentDeposit[], boolean, string] => { + const shouldUpdateState = useRef(true) + const [deposits, setDeposits] = useState([]) + const [isFetching, setIsFetching] = useState(false) + const [error, setError] = useState("") + + useEffect(() => { + const fetch = async () => { + if (!shouldUpdateState.current) return + setIsFetching(true) + + try { + const data = await subgraphAPI.fetchRecentTBTCDeposits(numberOfDeposits) + if (!shouldUpdateState.current) return + + setDeposits( + data.map((_) => ({ + amount: _.amount, + txHash: _.txHash, + date: _.timestamp, + address: _.address, + })) + ) + } catch (error) { + if (!shouldUpdateState.current) return + + setError((error as unknown as Error).toString()) + } finally { + if (!shouldUpdateState.current) return + + setIsFetching(false) + } + } + + fetch() + + return () => { + shouldUpdateState.current = false + } + }, [numberOfDeposits]) + + return [deposits, isFetching, error] +} diff --git a/src/hooks/useFetchTvl.ts b/src/hooks/useFetchTvl.ts index 811977b02..a145dbb59 100644 --- a/src/hooks/useFetchTvl.ts +++ b/src/hooks/useFetchTvl.ts @@ -1,4 +1,5 @@ import { useState, useCallback, useMemo } from "react" +import { BigNumberish } from "ethers" import { formatUnits } from "@ethersproject/units" import { useKeepBondingContract, @@ -15,51 +16,58 @@ import { toUsdBalance } from "../utils/getUsdBalance" interface TVLRawData { ecdsaTVL: string - tbtcTVL: string + tbtcv1TVL: string keepCoveragePoolTVL: string keepStakingTVL: string tStakingTVL: string + tBTC: string // TODO: add PRE and NU TVL } interface TVLData { ecdsa: string - tbtc: string + tbtcv1: string keepCoveragePool: string keepStaking: string tStaking: string + tBTC: string total: string } const initialState = { ecdsaTVL: "0", - tbtcTVL: "0", + tbtcv1TVL: "0", + tBTC: "0", keepCoveragePoolTVL: "0", keepStakingTVL: "0", tStakingTVL: "0", } -export const useFetchTvl = (): [TVLData, () => Promise] => { +export const useFetchTvl = (): [ + TVLData, + () => Promise, + TVLRawData +] => { const [rawData, setRawData] = useState(initialState) const { ecdsaTVL, - tbtcTVL, + tbtcv1TVL: tbtcTVL, keepCoveragePoolTVL, keepStakingTVL, tStakingTVL, + tBTC: tBTCTVL, } = rawData const eth = useETHData() const keep = useToken(Token.Keep) - const tbtc = useToken(Token.TBTC) + const tbtcv1 = useToken(Token.TBTC) const t = useToken(Token.T) const keepBonding = useKeepBondingContract() const multicall = useMulticallContract() const keepAssetPool = useKeepAssetPoolContract() const tTokenStaking = useTStakingContract() const keepTokenStaking = useKeepTokenStakingContract() - // TODO: fetch tbtcv2 data - // const tbtcv2 = useToken(Token.TBTCV2) + const tBTCToken = useToken(Token.TBTCV2) const fetchOnChainData = useMulticall([ { @@ -69,8 +77,8 @@ export const useFetchTvl = (): [TVLData, () => Promise] => { args: [keepBonding?.address], }, { - address: tbtc.contract?.address!, - interface: tbtc.contract?.interface!, + address: tbtcv1.contract?.address!, + interface: tbtcv1.contract?.interface!, method: "totalSupply", }, { @@ -90,6 +98,11 @@ export const useFetchTvl = (): [TVLData, () => Promise] => { method: "balanceOf", args: [tTokenStaking?.address], }, + { + address: tBTCToken.contract?.address!, + interface: tBTCToken.contract?.interface!, + method: "totalSupply", + }, ]) const fetchTVLData = useCallback(async () => { @@ -98,18 +111,20 @@ export const useFetchTvl = (): [TVLData, () => Promise] => { const [ ethInKeepBonding, - tbtcTokenTotalSupply, + tbtcv1TokenTotalSupply, coveragePoolTvl, keepStaking, tStaking, - ] = chainData.map((amount: string) => formatUnits(amount.toString())) + tBTCTokenTotalSupply, + ] = chainData.map((amount: BigNumberish) => amount.toString()) const data: TVLRawData = { ecdsaTVL: ethInKeepBonding, - tbtcTVL: tbtcTokenTotalSupply, + tbtcv1TVL: tbtcv1TokenTotalSupply, keepCoveragePoolTVL: coveragePoolTvl, keepStakingTVL: keepStaking, tStakingTVL: tStaking, + tBTC: tBTCTokenTotalSupply, } setRawData(data) @@ -117,30 +132,36 @@ export const useFetchTvl = (): [TVLData, () => Promise] => { }, [fetchOnChainData]) const data = useMemo(() => { - const ecdsa = toUsdBalance(ecdsaTVL, eth.usdPrice) + const ecdsa = toUsdBalance(formatUnits(ecdsaTVL), eth.usdPrice) - const tbtcUSD = toUsdBalance(tbtcTVL, tbtc.usdConversion) + const tbtcv1USD = toUsdBalance(formatUnits(tbtcTVL), tbtcv1.usdConversion) + const tBTCUSD = toUsdBalance(formatUnits(tBTCTVL), tBTCToken.usdConversion) const keepCoveragePool = toUsdBalance( - keepCoveragePoolTVL, + formatUnits(keepCoveragePoolTVL), keep.usdConversion ) - const keepStaking = toUsdBalance(keepStakingTVL, keep.usdConversion) + const keepStaking = toUsdBalance( + formatUnits(keepStakingTVL), + keep.usdConversion + ) - const tStaking = toUsdBalance(tStakingTVL, t.usdConversion) + const tStaking = toUsdBalance(formatUnits(tStakingTVL), t.usdConversion) return { ecdsa: ecdsa.toString(), - tbtc: tbtcUSD.toString(), + tbtcv1: tbtcv1USD.toString(), keepCoveragePool: keepCoveragePool.toString(), keepStaking: keepStaking.toString(), tStaking: tStaking.toString(), + tBTC: tBTCUSD.toString(), total: ecdsa - .addUnsafe(tbtcUSD) + .addUnsafe(tbtcv1USD) .addUnsafe(keepCoveragePool) .addUnsafe(keepStaking) .addUnsafe(tStaking) + .addUnsafe(tBTCUSD) .toString(), } as TVLData }, [ @@ -149,11 +170,13 @@ export const useFetchTvl = (): [TVLData, () => Promise] => { tbtcTVL, keepStakingTVL, tStakingTVL, + tBTCTVL, eth.usdPrice, keep.usdConversion, - tbtc.usdConversion, + tbtcv1.usdConversion, t.usdConversion, + tBTCToken.usdConversion, ]) - return [data, fetchTVLData] + return [data, fetchTVLData, rawData] } diff --git a/src/pages/Overview/Network/index.tsx b/src/pages/Overview/Network/index.tsx index 28ac9ff86..2306a5184 100644 --- a/src/pages/Overview/Network/index.tsx +++ b/src/pages/Overview/Network/index.tsx @@ -1,13 +1,15 @@ import { useEffect } from "react" import { SimpleGrid, Stack } from "@threshold-network/components" import TotalValueLocked from "./TotalValueLocked" -import WalletBalances from "./WalletBalances" import StakingOverview from "./StakingOverview" import { useFetchTvl } from "../../../hooks/useFetchTvl" import { PageComponent } from "../../../types" +import { TBTCBridgeStats } from "./tBTCBridgeStats" +import { useFetchRecentDeposits } from "../../../hooks/tbtc" const Network: PageComponent = () => { - const [data, fetchtTvlData] = useFetchTvl() + const [tvlInUSD, fetchtTvlData, tvlInTokenUnits] = useFetchTvl() + const [deposits, isFetching, error] = useFetchRecentDeposits() useEffect(() => { fetchtTvlData() @@ -15,10 +17,14 @@ const Network: PageComponent = () => { return ( - + - + ) diff --git a/src/pages/Overview/Network/tBTCBridgeStats.tsx b/src/pages/Overview/Network/tBTCBridgeStats.tsx new file mode 100644 index 000000000..39fc6b09c --- /dev/null +++ b/src/pages/Overview/Network/tBTCBridgeStats.tsx @@ -0,0 +1,25 @@ +import { FC } from "react" +import { Card, Divider, LabelSm } from "@threshold-network/components" +import { + ProtocolHistory, + ProtocolHistoryProps, + TVL, + TVLProps, +} from "../../../components/tBTC" + +type TBTCBridgeStatsProps = ProtocolHistoryProps & TVLProps + +export const TBTCBridgeStats: FC = ({ + tvl, + tvlInUSD, + deposits, +}) => { + return ( + + tBTC Bridge Stats + + + + + ) +} diff --git a/src/pages/tBTC/Bridge/BridgeActivity/index.tsx b/src/pages/tBTC/Bridge/BridgeActivity/index.tsx index 64bd293e4..ac37bad42 100644 --- a/src/pages/tBTC/Bridge/BridgeActivity/index.tsx +++ b/src/pages/tBTC/Bridge/BridgeActivity/index.tsx @@ -7,7 +7,6 @@ import { Image, LabelSm, List, - ListItem, useColorModeValue, Box, LinkBox, @@ -21,6 +20,7 @@ import emptyHistoryImageSrcDark from "../../../../static/images/tBTC-bridge-no-h import emptyHistoryImageSrcLight from "../../../../static/images/tBTC-bridge-no-history-light.svg" import { InlineTokenBalance } from "../../../../components/TokenBalance" import Link from "../../../../components/Link" +import { OutlineListItem } from "../../../../components/OutlineListItem" const bridgeActivityStatusToBadgeProps: Record< BridgeActivityStatus, @@ -64,20 +64,7 @@ export const BridgeActivityCard: FC> = ({ } const ActivityItemWrapper: FC = ({ children }) => ( - + {children} ) diff --git a/src/pages/tBTC/Bridge/MintingCard/MintingFlowRouter.tsx b/src/pages/tBTC/Bridge/MintingCard/MintingFlowRouter.tsx index 11a80d964..ba36ab218 100644 --- a/src/pages/tBTC/Bridge/MintingCard/MintingFlowRouter.tsx +++ b/src/pages/tBTC/Bridge/MintingCard/MintingFlowRouter.tsx @@ -10,11 +10,19 @@ import { useWeb3React } from "@web3-react/core" import SubmitTxButton from "../../../../components/SubmitTxButton" import { useModal } from "../../../../hooks/useModal" import { ModalType } from "../../../../enums" -import { BridgeContractLink } from "../../../../components/tBTC" +import { + BridgeContractLink, + ProtocolHistoryRecentDeposits, + ProtocolHistoryTitle, + ProtocolHistoryViewMoreLink, + TVL, +} from "../../../../components/tBTC" import { TbtcMintingCardTitle } from "../components/TbtcMintingCardTitle" import { useRemoveDepositData } from "../../../../hooks/tbtc/useRemoveDepositData" import { useAppDispatch } from "../../../../hooks/store" import { tbtcSlice } from "../../../../store/tbtc" +import { useFetchTvl } from "../../../../hooks/useFetchTvl" +import { useFetchRecentDeposits } from "../../../../hooks/tbtc" const MintingFlowRouterBase = () => { const dispatch = useAppDispatch() @@ -80,20 +88,46 @@ const MintingFlowRouterBase = () => { export const MintingFlowRouter: FC = () => { const { active } = useWeb3React() + const [tvlInUSD, fetchTvl, tvl] = useFetchTvl() + const [deposits] = useFetchRecentDeposits(3) + + useEffect(() => { + fetchTvl() + }, [fetchTvl]) return ( {active ? ( - + <> + + + + + ) : ( <> -
Connect wallet to mint tBTC
- + +
Ready to mint tBTC?
+ + + + + )} - - -
) } diff --git a/src/utils/date.ts b/src/utils/date.ts index d221d1c90..d6e2a3d9a 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -2,6 +2,9 @@ export const ONE_SEC_IN_MILISECONDS = 1000 export const ONE_MINUTE_IN_SECONDS = 60 export const ONE_HOUR_IN_SECONDS = 3600 export const ONE_DAY_IN_SECONDS = 86400 +export const ONE_WEEK_IN_SECONDS = ONE_DAY_IN_SECONDS * 7 +export const ONE_MONTH_IN_SECONDS = ONE_DAY_IN_SECONDS * 30 +export const ONE_YEAR_IN_SECONDS = ONE_DAY_IN_SECONDS * 365 export const dateToUnixTimestamp = (date: Date = new Date()) => { return Math.floor(date.getTime() / ONE_SEC_IN_MILISECONDS) @@ -29,3 +32,30 @@ export const formatDate = ( locales, options ) + +// unit, max diff, divisor +const unitsToDivisor: [Intl.RelativeTimeFormatUnit, number, number][] = [ + ["second", ONE_MINUTE_IN_SECONDS, 1], + ["minute", ONE_HOUR_IN_SECONDS, ONE_MINUTE_IN_SECONDS], + ["hour", ONE_DAY_IN_SECONDS, ONE_HOUR_IN_SECONDS], + ["day", ONE_WEEK_IN_SECONDS, ONE_DAY_IN_SECONDS], + ["week", ONE_MONTH_IN_SECONDS, ONE_WEEK_IN_SECONDS], + ["month", ONE_YEAR_IN_SECONDS, ONE_MONTH_IN_SECONDS], + ["year", Infinity, ONE_YEAR_IN_SECONDS], +] +const rtf = new Intl.RelativeTimeFormat("en-gb", { style: "short" }) + +export const getRelativeTime = (dateOrUnixTimestamp: Date | number): string => { + const time = + typeof dateOrUnixTimestamp === "number" + ? dateOrUnixTimestamp + : dateToUnixTimestamp(dateOrUnixTimestamp) + + const diff = Math.round(time - dateToUnixTimestamp()) + + const [unit, , divisor] = + unitsToDivisor.find(([, maxDiff]) => maxDiff > Math.abs(diff)) ?? + unitsToDivisor[0] + + return rtf.format(Math.floor(diff / divisor), unit) +} diff --git a/src/utils/subgraphAPI.ts b/src/utils/subgraphAPI.ts new file mode 100644 index 000000000..b03cfeca7 --- /dev/null +++ b/src/utils/subgraphAPI.ts @@ -0,0 +1,68 @@ +import axios from "axios" + +const TBTC_SUBGRAPH_URL = + "https://api.thegraph.com/subgraphs/name/suntzu93/threshold-tbtc" + +type TransactionRawResponse = { + // Actual transaction hash where the tBTC tokens were minted. + id: string + // Unix timestamps in seconds. + timestamp: string + deposits: { + actualAmountReceived: string + user: { + // ETH address + id: string + } + }[] +} + +const mapToRecentDepositData = (transaction: TransactionRawResponse) => { + const amount = transaction.deposits[0].actualAmountReceived + const address = transaction.deposits[0].user.id + const txHash = transaction.id + const timestamp = +transaction.timestamp + + return { + amount, + address, + txHash, + timestamp, + } +} + +const fetchRecentTBTCDeposits = async (numberOfDeposits = 4) => { + const response = await axios.post(TBTC_SUBGRAPH_URL, { + query: `query { + transactions( + where: { description: "Minting Finalized" } + first: ${numberOfDeposits} + orderBy: timestamp + orderDirection: desc + ) { + id + timestamp + deposits { + actualAmountReceived + user { + id + } + } + } + }`, + }) + + if (response.data && response.data.errors) { + const errorMsg = "Failed to fetch data from tBTC v2 subgraph API." + console.error(errorMsg, response.data.errors) + throw new Error(errorMsg) + } + + return (response.data.data.transactions as TransactionRawResponse[]).map( + mapToRecentDepositData + ) +} + +export const subgraphAPI = { + fetchRecentTBTCDeposits, +}