diff --git a/src/App.tsx b/src/App.tsx index 6b423012..53c0f127 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,8 +10,10 @@ import { RuntimeContext, useRuntime } from "./useRuntime"; import { ChainInfoContext, useChainInfoFromMetadataFile } from "./useChainInfo"; const Block = lazy(() => import("./execution/Block")); -const BlockList = lazy(() => import("./execution/BlockList")); const BlockTransactions = lazy(() => import("./execution/BlockTransactions")); +const DSBlock = lazy(() => import("./execution/DSBlock")); +const BlockList = lazy(() => import("./execution/BlockList")); +const DSBlockList = lazy(() => import("./execution/DSBlockList")); const Address = lazy(() => import("./execution/Address")); const Transaction = lazy(() => import("./execution/Transaction")); const AllContracts = lazy(() => import("./token/AllContracts")); @@ -58,12 +60,19 @@ const App = () => { path="block/:blockNumberOrHash" element={} /> + } + /> + } + /> } /> } + path="dsblocklist" element={ } /> } /> = ({sourcifyPresent}) => { { @@ -24,7 +25,14 @@ const Home: FC = () => { return ( <>
- +
+ + + + + + +
{provider?.network.chainId !== 11155111 && ( diff --git a/src/components/BlockNotFound.tsx b/src/components/BlockNotFound.tsx index 7afcec8a..47452214 100644 --- a/src/components/BlockNotFound.tsx +++ b/src/components/BlockNotFound.tsx @@ -7,7 +7,7 @@ type BlockNotFoundProps = { const BlockNotFound: React.FC = ({ blockNumberOrHash }) => ( -
Block "{blockNumberOrHash}" not found.
+
Tx Block "{blockNumberOrHash}" not found.
); diff --git a/src/components/DSBlockLink.tsx b/src/components/DSBlockLink.tsx new file mode 100644 index 00000000..c1d86804 --- /dev/null +++ b/src/components/DSBlockLink.tsx @@ -0,0 +1,35 @@ +import { FC, memo } from "react"; +import { NavLink } from "react-router-dom"; +import { BlockTag } from "@ethersproject/abstract-provider"; +import { commify } from "@ethersproject/units"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCube } from "@fortawesome/free-solid-svg-icons"; +import { dsBlockURL } from "../url"; + +type DSBlockLinkProps = { + blockTag: BlockTag; +}; + +const DSBlockLink: FC = ({ blockTag }) => { + const isNum = typeof blockTag === "number"; + let text = blockTag; + if (isNum) { + text = commify(blockTag); + } + + return ( + + + + + {text} + + ); +}; + +export default memo(DSBlockLink); diff --git a/src/components/DSBlockNotFound.tsx b/src/components/DSBlockNotFound.tsx new file mode 100644 index 00000000..3cb86619 --- /dev/null +++ b/src/components/DSBlockNotFound.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import ContentFrame from "./ContentFrame"; + +type DSBlockNotFoundProps = { + blockNumberOrHash: string; +}; + +const DSBlockNotFound: React.FC = ({ blockNumberOrHash }) => ( + +
DS Block "{blockNumberOrHash}" not found.
+
+); + +export default React.memo(DSBlockNotFound); diff --git a/src/execution/Block.tsx b/src/execution/Block.tsx index 7a24c1c5..9de0a48c 100644 --- a/src/execution/Block.tsx +++ b/src/execution/Block.tsx @@ -54,7 +54,7 @@ const Block: FC = () => {
- Block + Tx Block #{blockNumberOrHash} {block && ( { const { provider } = useContext(RuntimeContext); - const latestBlock = useLatestBlockHeader(provider); - + const latestBlockNum = useLatestBlockNumber(provider); const [feeDisplay, feeDisplayToggler] = useFeeToggler(); - const latestBlockNum = latestBlock?.number; const [searchParams] = useSearchParams(); @@ -43,7 +41,7 @@ const BlockList: React.FC = () => { return ( -
Block List
+
Tx Block List
{ + const { zilliqa } = useContext(RuntimeContext); + const { dsBlockNumberOrHash } = useParams(); + if (dsBlockNumberOrHash === undefined) { + throw new Error("dsBlockNumberOrHash couldn't be undefined here"); + } + + const { data: dsBlock, isLoading } = useDSBlockData(zilliqa, dsBlockNumberOrHash); + useBlockPageTitle(parseInt(dsBlockNumberOrHash)); + + const latestBlockChainInfo = useLatestBlockChainInfo(zilliqa); + const latestDSBlockNum = latestBlockChainInfo?.CurrentDSEpoch; + + return ( + + +
+ DS Block + #{dsBlockNumberOrHash} + {dsBlock && ( + + )} +
+
+ {dsBlock === null && ( + + )} + {dsBlock === undefined && ( + + Loading DS Block data... + + )} + {dsBlock && ( + + + {commify(dsBlock.header.BlockNum)} + + + + + + + + + {commify(dsBlock.header.GasPrice)} + + + {commify(dsBlock.header.Difficulty.toString())} + + + {commify(dsBlock.header.DifficultyDS.toString())} + + + + + + )} +
+ ); +}; + +export default DSBlock; diff --git a/src/execution/DSBlockList.tsx b/src/execution/DSBlockList.tsx new file mode 100644 index 00000000..16b4a0ae --- /dev/null +++ b/src/execution/DSBlockList.tsx @@ -0,0 +1,68 @@ +import React, { useContext } from "react"; +import { useSearchParams } from "react-router-dom"; +import StandardFrame from "../components/StandardFrame"; +import { PAGE_SIZE } from "../params"; +import { RuntimeContext } from "../useRuntime"; +import StandardSubtitle from "../components/StandardSubtitle"; +import { useLatestBlockChainInfo } from "../useLatestBlock"; +import { PendingRecentDSBlockResults } from "../search/PendingResults"; +import StandardSelectionBoundary from "../selection/StandardSelectionBoundary"; +import ContentFrame from "../components/ContentFrame"; +import { totalBlocksFormatter } from "../search/messages"; +import SearchResultNavBar from "../search/SearchResultNavBar"; +import { useDSBlocksData } from "../useZilliqaHooks"; +import RecentDSBlockItem from "../search/RecentDSBlockItem"; +import DSBlockResultHeader from "../search/DSBlockResultHeader"; +import DSBlockItem from "../search/DSBlockItem"; + +const DSBlockList: React.FC = () => { + const { zilliqa } = useContext(RuntimeContext); + + const latestBlockChainInfo = useLatestBlockChainInfo(zilliqa); + const latestBlockNum = latestBlockChainInfo?.CurrentDSEpoch; + const latestBlockNumInt = latestBlockNum !== undefined ? parseInt(latestBlockNum, 10) : undefined; + + const [searchParams] = useSearchParams(); + let pageNumber = 1; + const p = searchParams.get("p"); + if (p) { + try { + pageNumber = parseInt(p); + } catch (err) {} + } + + const { data, isLoading } = useDSBlocksData( + zilliqa, + latestBlockNumInt, + pageNumber - 1, + PAGE_SIZE + ); + + return ( + + +
DS Block List
+
+ + + + {data ? ( + + {data.map((block) => ( + block ? : <> + ))} + + ) : ( + + )} + +
+ ); +}; + +export default DSBlockList; diff --git a/src/execution/address/AddressTransactionResults.tsx b/src/execution/address/AddressTransactionResults.tsx index 94e2dec7..e764ce4c 100644 --- a/src/execution/address/AddressTransactionResults.tsx +++ b/src/execution/address/AddressTransactionResults.tsx @@ -7,7 +7,7 @@ import NativeTokenAmountAndFiat from "../../components/NativeTokenAmountAndFiat" import { balancePreset } from "../../components/FiatValue"; import TransactionAddressWithCopy from "../components/TransactionAddressWithCopy"; import TransactionLink from "../../components/TransactionLink"; -import PendingTransactionResults from "../../search/PendingTransactionResults"; +import { PendingTransactionResults } from "../../search/PendingResults"; import TransactionResultHeader from "../../search/TransactionResultHeader"; import { SearchController } from "../../search/search"; import TransactionItem from "../../search/TransactionItem"; diff --git a/src/execution/block/BlockTransactionResults.tsx b/src/execution/block/BlockTransactionResults.tsx index 25988e8b..b67cdff5 100644 --- a/src/execution/block/BlockTransactionResults.tsx +++ b/src/execution/block/BlockTransactionResults.tsx @@ -3,7 +3,7 @@ import ContentFrame from "../../components/ContentFrame"; import StandardSelectionBoundary from "../../selection/StandardSelectionBoundary"; import SearchResultNavBar from "../../search/SearchResultNavBar"; import TransactionResultHeader from "../../search/TransactionResultHeader"; -import PendingTransactionResults from "../../search/PendingTransactionResults"; +import { PendingTransactionResults } from "../../search/PendingResults"; import TransactionItem from "../../search/TransactionItem"; import { useFeeToggler } from "../../search/useFeeToggler"; import { totalTransactionsFormatter } from "../../search/messages"; diff --git a/src/execution/block/RecentBlocks.tsx b/src/execution/block/RecentBlocks.tsx index 05823a47..ef238fbe 100644 --- a/src/execution/block/RecentBlocks.tsx +++ b/src/execution/block/RecentBlocks.tsx @@ -4,11 +4,11 @@ import StandardSelectionBoundary from "../../selection/StandardSelectionBoundary import { useFeeToggler } from "../../search/useFeeToggler"; import { RuntimeContext } from "../../useRuntime"; import { useRecentBlocks } from "../../useErigonHooks"; -import { useLatestBlockHeader } from "../../useLatestBlock"; +import { useLatestBlockNumber } from "../../useLatestBlock"; import { RECENT_SIZE } from "../../params"; import RecentBlockItem from "../../search/RecentBlockItem"; import RecentBlockResultHeader from "../../search/RecentBlockResultHeader"; -import PendingBlockResults from "../../search/PendingBlockResults"; +import { PendingRecentBlockResults } from "../../search/PendingResults"; import RecentNavBar from "../../search/RecentNavBar"; @@ -16,8 +16,7 @@ const RecentBlocks: FC = () => { const { provider } = useContext(RuntimeContext); const [feeDisplay, feeDisplayToggler] = useFeeToggler(); - const latestBlock = useLatestBlockHeader(provider); - const latestBlockNum = latestBlock?.number; + const latestBlockNum = useLatestBlockNumber(provider); // Uses hook to get the most recent blocks const { data, isLoading } = useRecentBlocks( @@ -42,7 +41,7 @@ const RecentBlocks: FC = () => { ))} ) : ( - + )}
); diff --git a/src/execution/block/RecentDSBlocks.tsx b/src/execution/block/RecentDSBlocks.tsx new file mode 100644 index 00000000..b0ee3364 --- /dev/null +++ b/src/execution/block/RecentDSBlocks.tsx @@ -0,0 +1,46 @@ +import { FC, useContext, memo } from "react"; +import ContentFrame from "../../components/ContentFrame"; +import StandardSelectionBoundary from "../../selection/StandardSelectionBoundary"; +import { RuntimeContext } from "../../useRuntime"; +import { useDSBlocksData } from "../../useZilliqaHooks"; +import { RECENT_SIZE } from "../../params"; +import RecentDSBlockItem from "../../search/RecentDSBlockItem"; +import { PendingRecentDSBlockResults } from "../../search/PendingResults"; +import RecentDSNavBar from "../../search/RecentDSNavBar"; +import RecentDSBlockResultHeader from "../../search/RecentDSBlockResultHeader"; +import { useLatestBlockChainInfo } from "../../useLatestBlock"; + + +const RecentDSBlocks: FC = () => { + const { zilliqa } = useContext(RuntimeContext); + + const latestBlockChainInfo = useLatestBlockChainInfo(zilliqa); + const latestBlockNum = latestBlockChainInfo?.CurrentDSEpoch; + + // Uses hook to get the most recent blocks + const { data, isLoading } = useDSBlocksData( + zilliqa, + latestBlockNum !== undefined ? parseInt(latestBlockNum, 10) : undefined, + 0, + RECENT_SIZE + ); + + // Return a table with rows containing the basic information of the most recent RECENT_SIZE blocks + return ( + + + + {data ? ( + + {data.map((block) => ( + block ? : <> + ))} + + ) : ( + + )} + + ); +}; + +export default memo(RecentDSBlocks); diff --git a/src/search/BlockItem.tsx b/src/search/BlockItem.tsx index a5458b03..412ef458 100644 --- a/src/search/BlockItem.tsx +++ b/src/search/BlockItem.tsx @@ -31,7 +31,7 @@ const BlockItem: React.FC = ({ block, feeDisplay}) => { to={blockTxsURL(block.number)} > {block.transactionCount} transactions - {" "} + diff --git a/src/search/DSBlockItem.tsx b/src/search/DSBlockItem.tsx new file mode 100644 index 00000000..98a6682f --- /dev/null +++ b/src/search/DSBlockItem.tsx @@ -0,0 +1,46 @@ +import React from "react"; +import TimestampAge from "../components/TimestampAge"; +import { DsBlockObj } from '@zilliqa-js/core/dist/types/src/types' +import { commify } from "ethers/lib/utils"; +import { addHexPrefix, pubKeyToAddr, zilliqaToOtterscanTimestamp } from "../utils/utils"; +import DSBlockLink from "../components/DSBlockLink"; +import TransactionAddress from "../execution/components/TransactionAddress"; +import HexValue from "../components/HexValue"; + +type DSBlockItemProps = { + block: DsBlockObj; + selectedAddress?: string; +}; + +const DSBlockItem: React.FC = ( { block, selectedAddress } ) => { + + return ( +
+ + + + + {commify(block.header.Difficulty)} + + + {commify(block.header.DifficultyDS)} + + + + + + + + +
+ ); +}; + +export default DSBlockItem; diff --git a/src/search/DSBlockResultHeader.tsx b/src/search/DSBlockResultHeader.tsx new file mode 100644 index 00000000..c8d4e91d --- /dev/null +++ b/src/search/DSBlockResultHeader.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import { FeeDisplay } from "./useFeeToggler"; + +const DSBlockResultHeader: React.FC = () => ( +
+
Height
+
Difficulty
+
DS Difficulty
+
Age
+
DS Leader
+
Prev Block Hash
+
+); + +export default React.memo(DSBlockResultHeader); diff --git a/src/search/PendingBlockResults.tsx b/src/search/PendingBlockResults.tsx deleted file mode 100644 index 4ae2c6a5..00000000 --- a/src/search/PendingBlockResults.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; - -const PendingBlockResults: React.FC = () => ( -
-
-
-
-
-
-
-); - -export default React.memo(PendingBlockResults); diff --git a/src/search/PendingResults.tsx b/src/search/PendingResults.tsx new file mode 100644 index 00000000..c65edce9 --- /dev/null +++ b/src/search/PendingResults.tsx @@ -0,0 +1,45 @@ +import React from "react"; + +export const PendingRecentBlockResults: React.FC = React.memo(() => ( +
+
+
+
+
+
+
+)); + +export const PendingBlockResults: React.FC = React.memo(() => ( +
+
+
+
+
+
+
+
+)); + +export const PendingRecentDSBlockResults: React.FC = React.memo(() => ( +
+
+
+
+
+
+)); + +export const PendingTransactionResults: React.FC = React.memo(() => ( +
+
+
+
+
+
+
+
+
+
+)); + diff --git a/src/search/PendingTransactionResults.tsx b/src/search/PendingTransactionResults.tsx deleted file mode 100644 index 36b8170f..00000000 --- a/src/search/PendingTransactionResults.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from "react"; - -const PendingTransactionResults: React.FC = () => ( -
-
-
-
-
-
-
-
-
-
-); - -export default React.memo(PendingTransactionResults); diff --git a/src/search/RecentBlockItem.tsx b/src/search/RecentBlockItem.tsx index af928f18..a84ede35 100644 --- a/src/search/RecentBlockItem.tsx +++ b/src/search/RecentBlockItem.tsx @@ -31,7 +31,7 @@ const RecentBlockItem: React.FC = ({ block, feeDisplay}) => { to={blockTxsURL(block.number)} > {block.transactionCount} transactions - {" "} +
{feeDisplay === FeeDisplay.TX_FEE && formatValue(block.feeReward, 18)} diff --git a/src/search/RecentDSBlockItem.tsx b/src/search/RecentDSBlockItem.tsx new file mode 100644 index 00000000..1fde82aa --- /dev/null +++ b/src/search/RecentDSBlockItem.tsx @@ -0,0 +1,32 @@ +import DSBlockLink from "../components/DSBlockLink"; +import TimestampAge from "../components/TimestampAge"; +import { DsBlockObj } from '@zilliqa-js/core/dist/types/src/types' +import { commify } from "ethers/lib/utils"; +import { zilliqaToOtterscanTimestamp } from "../utils/utils"; + +type DSBlockItemProps = { + block: DsBlockObj, +}; + +const RecentDSBlockItem: React.FC = ({ block }) => { + + return ( +
+ + + + + {commify(block.header.Difficulty)} + + + {commify(block.header.DifficultyDS)} + + +
+ ); +}; + +export default RecentDSBlockItem; diff --git a/src/search/RecentDSBlockResultHeader.tsx b/src/search/RecentDSBlockResultHeader.tsx new file mode 100644 index 00000000..9c4890d4 --- /dev/null +++ b/src/search/RecentDSBlockResultHeader.tsx @@ -0,0 +1,12 @@ +import React from "react"; + +const RecentDSBlockResultHeader: React.FC = () => ( +
+
Height
+
Difficulty
+
Difficulty
+
Age
+
+); + +export default React.memo(RecentDSBlockResultHeader); diff --git a/src/search/RecentDSNavBar.tsx b/src/search/RecentDSNavBar.tsx new file mode 100644 index 00000000..8a952a04 --- /dev/null +++ b/src/search/RecentDSNavBar.tsx @@ -0,0 +1,25 @@ +import { FC, memo } from "react"; +import { NavLink } from "react-router-dom"; + +type RecentNavBarProps = { + isLoading : boolean +}; + +const RecentDSNavBar: FC = ({ isLoading }) => ( +
+
+ {isLoading + ? "Waiting for blocks..." + : "DS Blocks"} +
+ + {"View All"} + +
+); + +export default memo(RecentDSNavBar); diff --git a/src/search/search.ts b/src/search/search.ts index 702d4c0a..b6c4b3a3 100644 --- a/src/search/search.ts +++ b/src/search/search.ts @@ -241,6 +241,15 @@ const doSearch = async (q: string, navigate: NavigateFunction) => { return; } + // DS Block number? + if(q.charAt(0) === "#"){ + const dsBlockNumber = parseInt(q.substring(1)); + if(!isNaN(dsBlockNumber)) { + navigate(`/dsblock/${dsBlockNumber}`); + return; + } + } + // Epoch? if (q.startsWith("epoch:")) { const mayBeEpoch = q.substring(6); diff --git a/src/url.ts b/src/url.ts index 30dd8387..87f5d0de 100644 --- a/src/url.ts +++ b/src/url.ts @@ -30,6 +30,8 @@ export const slotAttestationsURL = (slotNumber: number) => export const validatorURL = (validatorIndex: number) => `/validator/${validatorIndex}`; +export const dsBlockURL = (blockNum: BlockTag) => `/dsblock/${blockNum}`; + export const blockURL = (blockNum: BlockTag) => `/block/${blockNum}`; export const blockTxsURL = (blockNum: BlockTag) => `/block/${blockNum}/txs`; diff --git a/src/useErigonHooks.ts b/src/useErigonHooks.ts index bd522e0a..63a41e72 100644 --- a/src/useErigonHooks.ts +++ b/src/useErigonHooks.ts @@ -24,6 +24,8 @@ import { TokenMeta, } from "./types"; import erc20 from "./erc20.json"; +import { DsBlockObj } from '@zilliqa-js/core/dist/types/src/types' +import { Zilliqa } from "@zilliqa-js/zilliqa"; const TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; diff --git a/src/useLatestBlock.ts b/src/useLatestBlock.ts index ef26cdea..579cb606 100644 --- a/src/useLatestBlock.ts +++ b/src/useLatestBlock.ts @@ -1,6 +1,11 @@ import { useState, useEffect } from "react"; import { Block } from "@ethersproject/abstract-provider"; import { JsonRpcProvider } from "@ethersproject/providers"; +import { Zilliqa } from "@zilliqa-js/zilliqa"; +import { BlockchainInfo } from '@zilliqa-js/core/dist/types/src/types' +import { useBlockChainInfo } from "./useZilliqaHooks"; + +const refreshRate = 30000 // In milliseconds /** * Returns the latest block header AND hook an internal listener @@ -77,3 +82,39 @@ export const useLatestBlockNumber = (provider?: JsonRpcProvider) => { return latestBlock; }; + +/** + * Returns the latest chain information AND hoook an internal listener + * that'll update and trigger a component reder as a side effect every + * the poll returns a different value + */ + +export const useLatestBlockChainInfo = (zilliqa?: Zilliqa) +: BlockchainInfo | undefined => { + const [latestBlockChainInfo, setLatestBlockChainInfo] = useState(); + + useEffect(() => { + // TODO: Is this necessary to check whether the hook has been removed + let isCancelled = false + if (!zilliqa) { + return; + } + + const getData = async () => { + const blockChainInfo = await zilliqa.blockchain.getBlockChainInfo(); + if (!isCancelled && blockChainInfo) { + setLatestBlockChainInfo(blockChainInfo.result); + } + } + getData() + const getDataTimer = setInterval(async () => { + await getData() + }, refreshRate) + return () => { + isCancelled = true + clearInterval(getDataTimer) + } + }, [zilliqa]); + + return latestBlockChainInfo; +}; diff --git a/src/useRuntime.ts b/src/useRuntime.ts index b3bd11b9..8226b875 100644 --- a/src/useRuntime.ts +++ b/src/useRuntime.ts @@ -6,6 +6,8 @@ import { import { OtterscanConfig, useConfig } from "./useConfig"; import { useProvider } from "./useProvider"; import { ConnectionStatus } from "./types"; +import { Zilliqa } from "@zilliqa-js/zilliqa"; +import { useZilliqa } from "./useZilliqa"; /** * A runtime comprises a OtterscanConfig read from somewhere, + @@ -27,6 +29,12 @@ export type OtterscanRuntime = { * probing occurring, etc. */ provider?: JsonRpcProvider; + + /** + * Zilliqa object; may be undefined if not ready because of config fetching, + * probing occurring, etc. + */ + zilliqa?: Zilliqa; }; export const useRuntime = (): OtterscanRuntime => { @@ -50,6 +58,8 @@ export const useRuntime = (): OtterscanRuntime => { effectiveConfig?.experimentalFixedChainId ); + const zilliqa = useZilliqa(effectiveConfig?.erigonURL); + const runtime = useMemo((): OtterscanRuntime => { if (effectiveConfig === undefined) { return { connStatus: ConnectionStatus.CONNECTING }; @@ -64,10 +74,11 @@ export const useRuntime = (): OtterscanRuntime => { effectiveConfig.erigonURL, effectiveConfig.experimentalFixedChainId ), + zilliqa : zilliqa, }; } - return { config: effectiveConfig, connStatus, provider }; - }, [effectiveConfig, connStatus, provider]); + return { config: effectiveConfig, connStatus, provider, zilliqa }; + }, [effectiveConfig, connStatus, provider, zilliqa]); return runtime; }; diff --git a/src/useZilliqa.ts b/src/useZilliqa.ts new file mode 100644 index 00000000..b6f33630 --- /dev/null +++ b/src/useZilliqa.ts @@ -0,0 +1,15 @@ +import { useMemo } from "react"; +import { Zilliqa } from '@zilliqa-js/zilliqa' + +export const useZilliqa = (erigonURL?: string): Zilliqa | undefined => { + + // Do I need to use useMemo here as string is a primitive type + const zilliqa = useMemo((): Zilliqa | undefined => { + if (erigonURL === undefined) { + return undefined; + } + return new Zilliqa(erigonURL); + }, [erigonURL]); + + return zilliqa; +}; diff --git a/src/useZilliqaHooks.ts b/src/useZilliqaHooks.ts new file mode 100644 index 00000000..1aeb88ab --- /dev/null +++ b/src/useZilliqaHooks.ts @@ -0,0 +1,93 @@ +// TODO: Once devex is completely depricated we can alter the zilliqa APIs so +// values are returned in a format that is easier to handle in Otterscan such +// as Timestamp + +import { Fetcher } from "swr"; +import useSWRImmutable from "swr/immutable"; +import useSWRInfinite from "swr/infinite"; +import { DsBlockObj, BlockchainInfo } from '@zilliqa-js/core/dist/types/src/types' +import { Zilliqa } from "@zilliqa-js/zilliqa"; + +const dsBlockDataFetcher: Fetcher< + DsBlockObj | null, + [Zilliqa, number] +> = async ([zilliqa, blockNum]) => { + const response = await zilliqa.blockchain.getDSBlock(blockNum); + if (response.error !== undefined) throw new Error(response.error.message); + if (response.result.header.BlockNum !== blockNum.toString()) + throw new Error("Invalid DS Block Number"); + return response.result as DsBlockObj; +}; + +export const useDSBlockData = ( + zilliqa: Zilliqa | undefined, + blockNumberOrHash: string | undefined +): { data: DsBlockObj | null | undefined; isLoading: boolean } => { + const { data, error, isLoading } = useSWRImmutable( + zilliqa !== undefined && blockNumberOrHash !== undefined + ? [zilliqa, blockNumberOrHash] + : null, + dsBlockDataFetcher, + { keepPreviousData: true } + ); + if (error) { + return { data: undefined, isLoading: false }; + } + return { data, isLoading }; +}; + +export const useDSBlocksData = ( + zilliqa : Zilliqa | undefined, + blockNumber: number | undefined, + pageNumber: number, + pageSize: number +): { data: (DsBlockObj | null)[] | undefined; isLoading: boolean } => { + const startBlockNum : number | undefined = blockNumber ? blockNumber - ( pageSize * pageNumber) : undefined; + + // This function is used by SWR to get the key which we pass to the fetcher function + // It also searches the cache for the presence of this key and if found returns the + // cached value. The pageSize differenciates the cache between components so that different components + // do not display incorrect number of displays + const getKey = (pageIndex : number) + : [ Zilliqa, number, number] | null => { + if((zilliqa == undefined || startBlockNum == undefined) + || (startBlockNum - pageIndex < 0)) return null; + + return [zilliqa, startBlockNum - pageIndex, pageSize] + } + + // Calls the fetcher to fetch the most recent pageNumber of blocks in parallel + const { data, error, isLoading, isValidating } = useSWRInfinite(getKey, + dsBlockDataFetcher, + { keepPreviousData: true, revalidateFirstPage : false, initialSize : pageSize, parallel : true } + ); + if (error) { + return { data: undefined, isLoading: false }; + } + return { data, isLoading : isLoading || isValidating}; +}; + +export const blockchainInfoFetcher: Fetcher = async (zilliqa : Zilliqa) => { + const response = await zilliqa.blockchain.getBlockChainInfo(); + if (response.error !== undefined) throw new Error(response.error.message); + return response.result as BlockchainInfo; +} + + +export const useBlockChainInfo = ( + zilliqa : Zilliqa | undefined +): { data: BlockchainInfo | undefined; isLoading: boolean } => { + const { data, error, isLoading } = useSWRImmutable( + zilliqa !== undefined + ? zilliqa + : null, + blockchainInfoFetcher, + { keepPreviousData: true } + ); + if (error) { + return { data: undefined, isLoading: false }; + } + return { data, isLoading }; +}; + diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 71144444..d282b207 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,3 +1,6 @@ +import { getAddressFromPublicKey, toBech32Address } from "@zilliqa-js/crypto"; +import { validation } from "@zilliqa-js/util"; + export const ageString = (durationInSecs: number) => { if (durationInSecs === 0) { return "now"; @@ -40,3 +43,29 @@ export const ageString = (durationInSecs: number) => { return desc; }; + +export const zilliqaToOtterscanTimestamp = (timestamp: string) : number => { + return Math.trunc(parseInt(timestamp, 10) / 1000000) +}; + +export const stripHexPrefix: (inputHex: string) => string = ( + inputHex: string +) => { + if (inputHex.substring(0, 2) === "0x") return inputHex.substring(2); + return inputHex; +}; + +// Add hex prefix if not already +export const addHexPrefix: (inputHex: string) => string = ( + inputHex: string +) => { + if (inputHex.substring(0, 2) !== "0x") return "0x" + inputHex; + return inputHex; +}; + +export const pubKeyToAddr: (k: string) => string = (pubKey: string) => { + const strippedPubKey = stripHexPrefix(pubKey); + if (!validation.isPubKey(strippedPubKey)) return "Invalid public key"; + else return getAddressFromPublicKey(strippedPubKey).toLowerCase(); +}; +