diff --git a/package.json b/package.json index 84e2496..f9c8a2b 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "prettier": "^2.2.1", "react-scripts": "4.0.3", "typescript": "^4.1.2", - "web-vitals": "^1.0.1" + "web-vitals": "^1.0.1", + "react-error-overlay": "6.0.9" } } diff --git a/src/api/client.ts b/src/api/client.ts index 84b964f..b218316 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -5,6 +5,7 @@ import { RPCStakingTransactionHarmony, RPCTransactionHarmony, RelatedTransaction, + Log, LogDetailed } from "src/types"; import { IHoldersInfo, @@ -71,7 +72,11 @@ export function getInternalTransactionsByField(params: any[]) { } export function getTransactionLogsByField(params: any[]) { - return transport("getLogsByField", params) as Promise; + return transport("getLogsByField", params) as Promise; +} + +export function getDetailedTransactionLogsByField(params: any[]) { + return transport("getDetailedLogsByField", params) as Promise; } export async function getByteCodeSignatureByHash(params: [string]) { diff --git a/src/components/tables/TransactionsTable.tsx b/src/components/tables/TransactionsTable.tsx index bf91e2a..a2e61ac 100644 --- a/src/components/tables/TransactionsTable.tsx +++ b/src/components/tables/TransactionsTable.tsx @@ -157,6 +157,7 @@ interface TransactionTableProps { primaryKey?: string; showPages?: boolean textType?: string + paginationOptions?: string[] } export function TransactionsTable(props: TransactionTableProps) { @@ -177,7 +178,8 @@ export function TransactionsTable(props: TransactionTableProps) { noScrollTop, minWidth = "1310px", showPages = false, - textType = 'transaction' + textType = 'transaction', + paginationOptions, } = props; const _IsLoading = isLoading; @@ -238,7 +240,7 @@ export function TransactionsTable(props: TransactionTableProps) { alwaysOpenedRowDetails={props.rowDetails ? true : false} tableProps={{ className: "g-table-header", - style: { width: "100%", minWidth }, + style: { width: "100%", minWidth, tableLayout: 'auto' }, columns: columns ? columns : getColumns({ history }), data: data, step, @@ -271,7 +273,7 @@ export function TransactionsTable(props: TransactionTableProps) { align="center" margin={{ top: "medium" }} > - + void; - options?: number[]; + options?: string[]; } const defaultOptions: string[] = ["10", "25", "50", "100"]; diff --git a/src/pages/AddressPage/index.tsx b/src/pages/AddressPage/index.tsx index 9bfa21b..023a7cb 100644 --- a/src/pages/AddressPage/index.tsx +++ b/src/pages/AddressPage/index.tsx @@ -31,6 +31,7 @@ import { getAddress } from "src/utils"; import { useCurrency } from "src/hooks/ONE-ETH-SwitcherHook"; import { HoldersTab } from "./tabs/holders/HoldersTab"; import { parseHexToText } from "../../web3/parseHex"; +import { EventsTab } from "./tabs/events/Events"; export function AddressPage() { const history = useHistory(); @@ -374,6 +375,12 @@ export function AddressPage() { ) : null} + {type === "erc20" && + Events}> + + + } + {/*{type === "erc1155" && inventory.length ? (*/} {/* Inventory ({inventory.length})}*/} diff --git a/src/pages/AddressPage/tabs/events/Events.tsx b/src/pages/AddressPage/tabs/events/Events.tsx new file mode 100644 index 0000000..0b25a73 --- /dev/null +++ b/src/pages/AddressPage/tabs/events/Events.tsx @@ -0,0 +1,278 @@ +import { Box, ColumnConfig, Spinner, Text, Tip } from "grommet"; +import React, { useEffect, useState } from "react"; +import { Link } from "react-router-dom"; +import dayjs from "dayjs"; +import { TransactionsTable } from "src/components/tables/TransactionsTable"; +import { Filter, IHexSignature, LogDetailed } from "src/types"; +import { + getByteCodeSignatureByHash, + getDetailedTransactionLogsByField +} from "../../../../api/client"; +import styled from "styled-components"; +import { DisplaySignature, parseSuggestedEvent } from "../../../../web3/parseByteCode"; + +const TopicsContainer = styled.div` + text-align: left; + font-family: monospace; + max-width: 85%; +` + +const TextEllipsis = styled.div` + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +` + +const LinkText = styled(TextEllipsis)` + color: ${props => props.theme.global.colors.brand}; +` + +const NeutralMarker = styled(Box)` + border-radius: 2px; + padding: 5px; + + text-align: center; + font-weight: bold; +`; + +const EventsBox = styled(Box)` + padding: 10px; + font-size: small; + + table { + tbody { + tr { + td { + vertical-align: top; + } + } + } + } +` + +const MethodSignature = styled.div` + word-break: break-word; + font-size: 14px; + font-family: monospace; +` + +const CenteredContainer = styled.div` + padding: 32px 24px; + display: flex; + justify-content: center; + align-items: center; +` + +function TxsHashColumn (props: { log: LogWithSignature }) { + const { log } = props + return
+ + + {log.transactionHash} + + + +
+ #  + + {log.blockNumber} + +
+
+ {log.timestamp && + +
{dayjs(log.timestamp).fromNow()}
+
+ } +
+} + +function TxMethod (props: { log: LogWithSignature }) { + const { inputSignatures } = props.log + + return
+ + + + {props.log.input.slice(0, 10)} + + + + {inputSignatures && inputSignatures.length > 0 && + + {inputSignatures[0].signature} + + } +
+} + +const Topic = styled(TextEllipsis)<{ isMarked: boolean }>` + opacity: ${(props) => props.isMarked ? 0.6 : 1}; +` + +function Topics (props: { log: LogWithSignature }) { + const { log } = props + const { topics, data, signatures } = log + let parsedEvents + try { + parsedEvents = signatures.map(s => s.signature).map(s => parseSuggestedEvent(s, data, topics)) + } catch (err) { + } + + const displaySignature = parsedEvents && parsedEvents[0] && DisplaySignature(parsedEvents[0]) || null + return + {displaySignature && +
+ {displaySignature} +
+ } + {log.topics.map((topic, i) => { + return + + [topic{i}] {topic} + + + })} +
+} + +const getColumns = (): ColumnConfig[] => { + return [ + { + property: "transactionHash", + header: ( + Txn Hash + ), + render: (data) => , + }, + { + property: "method", + header: ( + Method + ), + render: (data) => , + }, + { + property: "topics", + header: ( + + Topics + + ), + render: (data) => { + return + }, + }, + ]; +}; + +interface LogWithSignature extends LogDetailed { + signatures: IHexSignature[] + inputSignatures: IHexSignature[] +} + +export function EventsTab(props: { + id: string; +}) { + const limitValue = 10 // localStorage.getItem("tableLimitValue"); + + const initFilter: Partial = { + offset: 0, + limit: limitValue ? +limitValue : 10, + }; + + const [filter, setFilter] = useState({ + ...(initFilter as any), + }); + + const [isInitialLoading, setInitialLoading] = useState(true) + const [logs, setLogs] = useState([]) + + const loadEvents = async () => { + try { + const logs = await getDetailedTransactionLogsByField([ + 0, + "address", + props.id, + filter.limit, + filter.offset + ]); + + const logsSignatures = await Promise.all( + logs.map((l) => { + if (l.topics && l.topics.length > 0 && l.topics[0].length > 10) { + return getByteCodeSignatureByHash([l.topics[0].slice(0, 10)]) + } + return [] + }) + ) + + const inputSignatures = await Promise.all( + logs.map( (l) => { + if (l.input && l.input.length > 10) { + return getByteCodeSignatureByHash([l.input.slice(0, 10)]) + } + return [] + }) + ) + + const logsWithSignatures = logs.map((l, i: number) => ({ + ...l, + input: l.input || 'n/a', + timestamp: l.timestamp || '', + primaryKey: `${l.address}_${i}`, // key for grommet DataTable + signatures: logsSignatures[i], + inputSignatures: inputSignatures[i] + })); + + setLogs(logsWithSignatures); + } catch (e) { + console.error('Cannot get logs for address', (e as Error).message) + } finally { + setInitialLoading(false) + } + } + + useEffect(() => { + loadEvents() + }, [props.id]) + + useEffect(() => { + loadEvents() + }, [filter.offset]) + + if(isInitialLoading) { + return + + + } + + return ( + + + + ); +} diff --git a/src/pages/AllBlocksPage/AllBlocksTable.tsx b/src/pages/AllBlocksPage/AllBlocksTable.tsx index 62226a6..1346eef 100644 --- a/src/pages/AllBlocksPage/AllBlocksTable.tsx +++ b/src/pages/AllBlocksPage/AllBlocksTable.tsx @@ -1,6 +1,4 @@ import React, { useEffect, useState } from "react"; -import dayjs from "dayjs"; - import { Box, DataTable, Text, Spinner } from "grommet"; import { Block, Filter } from "src/types"; import { useHistory, useParams } from "react-router-dom"; @@ -11,8 +9,7 @@ import { PaginationBlockNavigator, PaginationRecordsPerPage, } from "src/components/ui"; -import { getBlocks, getCount } from "src/api/client"; -import { ShardDropdown } from "src/components/ui/ShardDropdown"; +import { getBlocks } from "src/api/client"; function getColumns(props: any) { const { history, shardNumber } = props; diff --git a/src/pages/ERC721List/ERC721Table.tsx b/src/pages/ERC721List/ERC721Table.tsx index e7124eb..68094b4 100644 --- a/src/pages/ERC721List/ERC721Table.tsx +++ b/src/pages/ERC721List/ERC721Table.tsx @@ -9,7 +9,6 @@ import { PaginationNavigator, PaginationRecordsPerPage, TipContent, - TokenValue, TPaginationAction, } from "src/components/ui"; import { Erc20 } from "../../hooks/ERC20_Pool"; diff --git a/src/pages/MainPage/LatestTransactionsTable.tsx b/src/pages/MainPage/LatestTransactionsTable.tsx index 2a5b883..9dc39ae 100644 --- a/src/pages/MainPage/LatestTransactionsTable.tsx +++ b/src/pages/MainPage/LatestTransactionsTable.tsx @@ -3,10 +3,10 @@ import React, { useEffect, useState } from "react"; import { Box, DataTable, Spinner, Text } from "grommet"; import { RPCTransactionHarmony } from "src/types"; import { useHistory } from "react-router-dom"; -import { RelativeTimer, Address } from "src/components/ui"; +import { Address } from "src/components/ui"; import { getTransactions } from "src/api/client"; import { FormNextLink } from "grommet-icons"; -import { DateTime } from "../../components/ui/DateTime"; +import { DateTime } from "../../components/ui"; function getColumns(props: any) { const { history } = props; diff --git a/src/pages/TransactionPage/index.tsx b/src/pages/TransactionPage/index.tsx index 994b8f0..6332ad1 100644 --- a/src/pages/TransactionPage/index.tsx +++ b/src/pages/TransactionPage/index.tsx @@ -13,8 +13,7 @@ import { getTransactionLogsByField, getByteCodeSignatureByHash, } from "src/api/client"; -import { AllBlocksTable } from "../AllBlocksPage/AllBlocksTable"; -import { parseSuggestedMethod, revertErrorMessage } from "src/web3/parseByteCode"; +import { revertErrorMessage } from "src/web3/parseByteCode"; import { hmyv2_getTransactionReceipt } from "src/api/rpc"; const extractError = (err: any) => { diff --git a/src/pages/VerifyContract/VerifyContract.tsx b/src/pages/VerifyContract/VerifyContract.tsx index fbdde4c..6657199 100644 --- a/src/pages/VerifyContract/VerifyContract.tsx +++ b/src/pages/VerifyContract/VerifyContract.tsx @@ -8,11 +8,10 @@ import { TextInput, } from "grommet"; import React from "react"; -import { BaseContainer, BasePage, Button } from "src/components/ui"; +import { BasePage, Button } from "src/components/ui"; import styled from "styled-components"; import { IVerifyContractData, verifyContractCode } from "src/api/explorerV1"; -import { CircleAlert, StatusGood, SubtractCircle } from "grommet-icons"; -import { toaster } from "src/App"; +import { SubtractCircle } from "grommet-icons"; import { breakpoints } from "../../Responive/breakpoints"; import { useMediaQuery } from "react-responsive"; import { useHistory } from "react-router"; diff --git a/src/types/blockchain.ts b/src/types/blockchain.ts index 23c20c2..a25abde 100644 --- a/src/types/blockchain.ts +++ b/src/types/blockchain.ts @@ -173,6 +173,11 @@ export type Log = { removed: boolean; }; +export interface LogDetailed extends Log { + input: string + timestamp: string +} + export type TraceCallTypes = | "CALL" | "STATICCALL" diff --git a/src/web3/parseByteCode.tsx b/src/web3/parseByteCode.tsx index 9d60148..c92031c 100644 --- a/src/web3/parseByteCode.tsx +++ b/src/web3/parseByteCode.tsx @@ -1,8 +1,8 @@ import Web3 from "web3"; -import { Box, Text } from "grommet"; +import { Text } from "grommet"; import React from "react"; import { Address } from "../components/ui"; -import { ByteCode, IHexSignature, InternalTransaction } from "../types"; +import { ByteCode, IHexSignature } from "../types"; const web3 = new Web3();