diff --git a/packages/cacti-ledger-browser/index.html b/packages/cacti-ledger-browser/index.html index 066d32e3cb..84e2a7a8d1 100644 --- a/packages/cacti-ledger-browser/index.html +++ b/packages/cacti-ledger-browser/index.html @@ -3,7 +3,7 @@ - + Cacti Ledger Browser diff --git a/packages/cacti-ledger-browser/package.json b/packages/cacti-ledger-browser/package.json index e44a72919f..88e03bd695 100644 --- a/packages/cacti-ledger-browser/package.json +++ b/packages/cacti-ledger-browser/package.json @@ -29,6 +29,11 @@ "email": "your.name@example.com", "url": "https://example.com" }, + { + "name": "Michal Bajer", + "email": "michal.bajer@fujitsu.com", + "url": "https://www.fujitsu.com/global/" + }, { "name": "Tomasz Awramski", "email": "tomasz.awramski@fujitsu.com", @@ -56,18 +61,16 @@ "@emotion/react": "11.11.4", "@emotion/styled": "11.11.5", "@mui/icons-material": "5.15.10", + "@mui/lab": "5.0.0-alpha.170", "@mui/material": "5.15.15", "@supabase/supabase-js": "1.35.6", "@tanstack/react-query": "5.29.2", "apexcharts": "3.45.2", - "localforage": "1.10.0", - "match-sorter": "6.3.3", - "moment": "2.30.1", + "ethers": "6.12.1", "react": "18.2.0", "react-apexcharts": "1.4.1", "react-dom": "18.2.0", "react-router-dom": "6.21.3", - "sort-by": "1.2.0", "web3": "4.1.1" }, "devDependencies": { @@ -75,7 +78,6 @@ "@tanstack/react-query-devtools": "5.29.2", "@types/react": "18.2.43", "@types/react-dom": "18.2.17", - "@types/sort-by": "1", "@vitejs/plugin-react": "4.2.1", "typescript": "5.2.2", "vite": "5.1.7" diff --git a/packages/cacti-ledger-browser/public/hyperledgerfavicon.webp b/packages/cacti-ledger-browser/public/hyperledgerfavicon.webp new file mode 100644 index 0000000000..f169b55239 Binary files /dev/null and b/packages/cacti-ledger-browser/public/hyperledgerfavicon.webp differ diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/AccountERC20View.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/AccountERC20View.tsx new file mode 100644 index 0000000000..5d052afcd8 --- /dev/null +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/AccountERC20View.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import Divider from "@mui/material/Divider"; + +import { TokenERC20 } from "../../../../common/supabase-types"; +import ERC20TokenList from "./ERC20TokenList"; +import ERC20TokenDetails from "./ERC20TokenDetails"; + +export type AccountERC20ViewProps = { + accountAddress: string; +}; + +export default function AccountERC20View({ + accountAddress, +}: AccountERC20ViewProps) { + const [selectedToken, setSelectedToken] = React.useState< + TokenERC20 | undefined + >(undefined); + + const tokenDetailsId = selectedToken + ? `${selectedToken.token_address}-${selectedToken.account_address}` + : "token-not-selected"; + + return ( + + + ERC20 + + + + setSelectedToken(token)} + /> + + + + + + + + + + ); +} diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/ERC20BalanceHistoryChart.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/ERC20BalanceHistoryChart.tsx new file mode 100644 index 0000000000..ab092df29e --- /dev/null +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/ERC20BalanceHistoryChart.tsx @@ -0,0 +1,93 @@ +import Chart from "react-apexcharts"; +import { useTheme } from "@mui/material"; + +import { BalanceHistoryListData } from "./balanceHistory"; + +export type ERC20BalanceHistoryChartProps = { + data: BalanceHistoryListData[]; + height?: string | number; +}; + +export default function ERC20BalanceHistoryChart({ + data, + height, +}: ERC20BalanceHistoryChartProps) { + if (!data) { + return; + } + + const theme = useTheme(); + + return ( + <> + {/* Style overwrite for Apex Charts to use colors from the theme for toolbar buttons (right-top of the chart) */} + + + {/* Chart component */} + new Date(txn.created_at).getTime()), + labels: { + format: "dd-MM-yyyy h:mm", + }, + }, + yaxis: { + title: { + text: "Balance", + }, + }, + stroke: { + curve: "stepline", + }, + markers: { + size: 6, + }, + tooltip: { + x: { + format: "dd-MM-yyyy h:mm:ss", + }, + }, + }} + series={[ + { + name: "Balance", + data: data.map((txn) => txn.balance), + }, + ]} + type="line" + height={height} + /> + + ); +} diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/ERC20BalanceHistoryTable.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/ERC20BalanceHistoryTable.tsx new file mode 100644 index 0000000000..730c5afd41 --- /dev/null +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/ERC20BalanceHistoryTable.tsx @@ -0,0 +1,168 @@ +import * as React from "react"; +import { styled } from "@mui/material/styles"; +import Box from "@mui/material/Box"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableFooter from "@mui/material/TableFooter"; +import TablePagination from "@mui/material/TablePagination"; +import TableRow from "@mui/material/TableRow"; +import Paper from "@mui/material/Paper"; +import TableHead from "@mui/material/TableHead"; +import Typography from "@mui/material/Typography"; +import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown"; +import ArrowDropUpIcon from "@mui/icons-material/ArrowDropUp"; + +import { TokenHistoryItem20 } from "../../../../common/supabase-types"; +import ShortenedTypography from "../../../../components/ui/ShortenedTypography"; + +const StyledHeaderCell = styled(TableCell)(({ theme }) => ({ + color: theme.palette.primary.main, + fontWeight: "bold", +})); + +export type BalanceAmountTextProps = { + children: React.ReactNode; +}; + +function PositiveBalanceAmountText({ children }: BalanceAmountTextProps) { + return ( + + + {children} + + + + ); +} + +function NegativeBalanceAmountText({ children }: BalanceAmountTextProps) { + return ( + + + {children} + + + + ); +} + +export type ERC20BalanceHistoryTableProps = { + data: TokenHistoryItem20[]; + ownerAddress: string; +}; + +function formatCreatedAtDate(createdAt: string) { + const date = new Date(createdAt); + return date.toUTCString(); +} + +export default function ERC20BalanceHistoryTable({ + data, + ownerAddress, +}: ERC20BalanceHistoryTableProps) { + const [page, setPage] = React.useState(0); + const [rowsPerPage, setRowsPerPage] = React.useState(5); + const sortedData = [...data].sort((a, b) => + b.created_at.localeCompare(a.created_at), + ); + + // Avoid a layout jump when reaching the last page with empty rows. + const emptyRows = + page > 0 ? Math.max(0, (1 + page) * rowsPerPage - sortedData.length) : 0; + + return ( + + + + + Time + Hash + From/To + Amount + + + + {(rowsPerPage > 0 + ? sortedData.slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage, + ) + : sortedData + ).map((row) => { + const isReceiving = row.recipient === ownerAddress; + + return ( + + {formatCreatedAtDate(row.created_at)} + + + + + {isReceiving ? ( + + ) : ( + + )} + + + {isReceiving ? ( + + {row.value} + + ) : ( + + -{row.value} + + )} + + + ); + })} + {emptyRows > 0 && ( + + + + )} + + + + { + setPage(newPage); + }} + onRowsPerPageChange={(event) => { + setRowsPerPage(parseInt(event.target.value, 10)); + setPage(0); + }} + /> + + +
+
+ ); +} diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/ERC20TokenDetails.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/ERC20TokenDetails.tsx new file mode 100644 index 0000000000..184cf3ba82 --- /dev/null +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/ERC20TokenDetails.tsx @@ -0,0 +1,115 @@ +import React from "react"; +import { useQuery } from "@tanstack/react-query"; +import Box from "@mui/material/Box"; +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import Typography from "@mui/material/Typography"; +import Skeleton from "@mui/material/Skeleton"; + +import { ethERC20TokenHistory } from "../../queries"; +import { TokenERC20 } from "../../../../common/supabase-types"; +import ShortenedTypography from "../../../../components/ui/ShortenedTypography"; +import { useNotification } from "../../../../common/context/NotificationContext"; +import ERC20BalanceHistoryChart from "./ERC20BalanceHistoryChart"; +import ERC20BalanceHistoryTable from "./ERC20BalanceHistoryTable"; +import { createBalanceHistoryList } from "./balanceHistory"; + +function TokenDetailsPlaceholder() { + return ( + + + Click on a token from the list on the left to display it's details. + + + ); +} + +export type ERC20TokenDetailsHeaderProps = { + token: TokenERC20; +}; + +function ERC20TokenDetailsHeader({ token }: ERC20TokenDetailsHeaderProps) { + return ( + + + + {token.name} [{token.symbol}] + + + Contract: + + + Supply: {token.total_supply} + Balance: {token.balance} + + + ); +} + +export type ERC20TokenDetailsProps = { + token?: TokenERC20; +}; + +export default function ERC20TokenDetails({ token }: ERC20TokenDetailsProps) { + if (!token) { + return ; + } + + const { showNotification } = useNotification(); + + const { isError, isPending, data, error } = useQuery( + ethERC20TokenHistory(token.token_address, token.account_address), + ); + const txData = data ?? []; + + React.useEffect(() => { + isError && + showNotification(`Could get ERC20 balance history: ${error}`, "error"); + }, [isError]); + + const balanceHistory = createBalanceHistoryList( + txData, + token.account_address, + ); + + return ( + + {/* Token information header */} + + {isPending ? ( + + ) : ( + + )} + + + {/* Balance history chart */} + + + + + {/* Balance history table */} + + + Token History + + + + + ); +} diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/ERC20TokenList.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/ERC20TokenList.tsx new file mode 100644 index 0000000000..50208a7563 --- /dev/null +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/ERC20TokenList.tsx @@ -0,0 +1,133 @@ +import * as React from "react"; +import { useQuery } from "@tanstack/react-query"; +import { styled } from "@mui/material/styles"; +import Box from "@mui/material/Box"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableFooter from "@mui/material/TableFooter"; +import TablePagination from "@mui/material/TablePagination"; +import TableRow from "@mui/material/TableRow"; +import Paper from "@mui/material/Paper"; +import TableHead from "@mui/material/TableHead"; +import CircularProgress from "@mui/material/CircularProgress"; + +import { useNotification } from "../../../../common/context/NotificationContext"; +import { TokenERC20 } from "../../../../common/supabase-types"; +import { ethAllERC20TokensByAccount } from "../../queries"; + +const StyledHeaderCell = styled(TableCell)(({ theme }) => ({ + color: theme.palette.primary.main, + fontWeight: "bold", +})); + +export type ERC20TokenListProps = { + accountAddress: string; + onTokenSelected: (token?: TokenERC20) => void; +}; + +export default function ERC20TokenList({ + accountAddress, + onTokenSelected, +}: ERC20TokenListProps) { + const { isError, isPending, data, error } = useQuery( + ethAllERC20TokensByAccount(accountAddress), + ); + const [page, setPage] = React.useState(0); + const [rowsPerPage, setRowsPerPage] = React.useState(5); + const { showNotification } = useNotification(); + const tokenList = data ?? []; + + React.useEffect(() => { + isError && + showNotification(`Could get ERC20 balance list: ${error}`, "error"); + }, [isError]); + + // Avoid a layout jump when reaching the last page with empty rows. + const emptyRows = + page > 0 ? Math.max(0, (1 + page) * rowsPerPage - tokenList.length) : 0; + + return ( + + + {isPending && ( + + )} + + + + Name + Symbol + Balance + + + + {(rowsPerPage > 0 + ? tokenList.slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage, + ) + : tokenList + ).map((row) => ( + { + onTokenSelected( + tokenList.find( + (t) => + t.account_address === row.account_address && + t.token_address === t.token_address, + ), + ); + }} + key={row.name} + > + {row.name} + {row.symbol} + {row.balance} + + ))} + {emptyRows > 0 && ( + + + + )} + + + + { + setPage(newPage); + }} + onRowsPerPageChange={(event) => { + setRowsPerPage(parseInt(event.target.value, 10)); + setPage(0); + }} + /> + + +
+
+
+ ); +} diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/balanceHistory.ts b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/balanceHistory.ts new file mode 100644 index 0000000000..36e62f745a --- /dev/null +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC20View/balanceHistory.ts @@ -0,0 +1,34 @@ +import { TokenHistoryItem20 } from "../../../../common/supabase-types"; + +export type BalanceHistoryListData = { + created_at: string; + balance: number; +}; + +/** + * Create list of total token balance history after any operation (send / receive). + * Can be used to graph balance history. + */ +export function createBalanceHistoryList( + txHistory: TokenHistoryItem20[], + ownerAddress: string, +) { + if (!txHistory) { + return []; + } + + let balance = 0; + const balances = txHistory.map((txn) => { + let txn_value = txn.value || 0; + if (txn.recipient !== ownerAddress) { + txn_value *= -1; + } + balance += txn_value; + return { + created_at: txn.created_at + "Z", + balance: balance, + }; + }); + + return balances as BalanceHistoryListData[]; +} diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC721View/AccountERC721View.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC721View/AccountERC721View.tsx new file mode 100644 index 0000000000..cf16a3de9a --- /dev/null +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC721View/AccountERC721View.tsx @@ -0,0 +1,61 @@ +import * as React from "react"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import CircularProgress from "@mui/material/CircularProgress"; +import { useQuery } from "@tanstack/react-query"; + +import { ethAllERC721TokensByAccount } from "../../queries"; +import { useNotification } from "../../../../common/context/NotificationContext"; +import NFTCard from "./NFTCard"; + +export type AccountERC721ViewProps = { + accountAddress: string; +}; + +export default function AccountERC721View({ + accountAddress, +}: AccountERC721ViewProps) { + const { isError, isPending, data, error } = useQuery( + ethAllERC721TokensByAccount(accountAddress), + ); + const { showNotification } = useNotification(); + const tokenList = data ?? []; + + React.useEffect(() => { + isError && showNotification(`Could get NFT list: ${error}`, "error"); + }, [isError]); + + return ( + + + ERC721 + + + {isPending && ( + + )} + {tokenList.map((t) => { + return ( + + ); + })} + + + ); +} diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC721View/NFTCard.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC721View/NFTCard.tsx new file mode 100644 index 0000000000..cd1acaa638 --- /dev/null +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/AccountERC721View/NFTCard.tsx @@ -0,0 +1,74 @@ +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import CardMedia from "@mui/material/CardMedia"; +import Typography from "@mui/material/Typography"; +import List from "@mui/material/List"; +import ListItem from "@mui/material/ListItem"; +import ListItemText from "@mui/material/ListItemText"; + +import { EthAllERC721TokensByAccountResponseType } from "../../queries"; +import ShortenedTypography from "../../../../components/ui/ShortenedTypography"; +import ShortenedLink from "../../../../components/ui/ShortenedLink"; +import nftPlaceholderImage from "../../static/nft-placeholder.png"; + +export type NFTCardProps = { + tokenDetails: EthAllERC721TokensByAccountResponseType; +}; + +export default function NFTCard({ tokenDetails }: NFTCardProps) { + return ( + + + + + {tokenDetails.token_metadata_erc721.name} #{tokenDetails.token_id} + + + + + + {tokenDetails.token_metadata_erc721.symbol} + + + + + + + {tokenDetails.uri && ( + + + + + )} + + + + ); +} diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/BlockList/BlockList.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/BlockList/BlockList.tsx index c5b12a14cb..252bafeeca 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/BlockList/BlockList.tsx +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/BlockList/BlockList.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { ethereumAllBlocksQuery } from "../../queries"; +import { ethAllBlocksQuery } from "../../queries"; import { blockColumnsConfig } from "./blockColumnsConfig"; import type { UITableListingPaginationActionProps } from "../../../../components/ui/UITableListing/UITableListingPaginationAction"; import UITableListing from "../../../../components/ui/UITableListing/UITableListing"; @@ -34,7 +34,7 @@ const BlockList: React.FC = ({ }) => { return ( span { - margin-top: 1rem; - font-size: 24px; -} - -.chart-wrapper-line{ - padding:1rem; - width: 100%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - background-color: rgb(253, 253, 253); - border-radius: 10px; - border: 1px solid rgb(238, 238, 238); -} \ No newline at end of file diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/Chart/Chart.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/Chart/Chart.tsx deleted file mode 100644 index dea1604765..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/Chart/Chart.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import Chart from "react-apexcharts"; - - -import styles from "./Chart.module.css"; -import { ERC20Txn } from "../../../../common/supabase-types"; - -function ApexChart(props: any) { - const chartProps = { - options: { - chart: { - id: "chart-example", - }, - xaxis: { - categories: props.chartData?.map((txn: ERC20Txn) => txn.token_address), - }, - }, - series: [ - { - name: "balance", - data: props.chartData?.map((txn: ERC20Txn) => txn.balance), - }, - ], - }; - - console.log(chartProps.options); - return ( -
- -
- ); -} - -export default ApexChart; diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/Chart/LineChart.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/Chart/LineChart.tsx deleted file mode 100644 index 48da2fe054..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/Chart/LineChart.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import Chart from "react-apexcharts"; -import moment from "moment"; - -import styles from "./Chart.module.css"; -import { useEffect, useState } from "react"; -import { balanceDate } from "../../../../common/supabase-types"; - -interface ChartProps { - series: { - list: { - name: string; - data: any[]; - }[]; - }; - options: { - stroke: Record; - tooltip: Record; - chart: { - id: string; - }; - xaxis: { - type: "datetime" | "category" | "numeric" | undefined; - categories: any[]; - labels: Record; - }; - }; -} - -function LineChart(props:any ) { - const [chartProps, setChartProps] = useState({ - series: { - list: [ - { - name: "balance", - data: [], - }, - ], - }, - options: { - stroke: {}, - tooltip: { - }, - chart: { - id: "chart-example", - }, - xaxis: { - type: undefined, - categories: [], - labels: {} - }, - }, - }); - - useEffect(() => { - const { chartData } = props; - - setChartProps({ - options: { - chart: { - id: "chart-example", - }, - stroke: { - curve: "stepline", - }, - tooltip: { - x: { - show: true, - format: "dd MM yyyy h:mm", - formatter: undefined, - }, - }, - xaxis: { - type: "datetime", - categories: chartData?.map((txn: balanceDate) => - moment(txn.created_at).format("YYYY-MM-DD h:mm:ss a"), - ), - labels: { - format: "dd MM yyyy h:mm", - }, - }, - }, - series: { - list: [ - { - name: "balance", - data: chartData?.map((txn: balanceDate) => txn.balance), - }, - ], - }, - }); - }); - - return ( -
- -
- ); -} - -export default LineChart; diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/TokenHeader/TokenAccount.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/TokenHeader/TokenAccount.tsx deleted file mode 100644 index c0b9b6be85..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/TokenHeader/TokenAccount.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import styles from "./TokenHeader.module.css"; -import AccountBalanceWalletIcon from "@mui/icons-material/AccountBalanceWallet"; - -function TokenAccount(props: Record) { - return ( -
- - {" "} - - - {" "} - {props.accountNum} - -
- ); -} - -export default TokenAccount; diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/TokenHeader/TokenHeader.module.css b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/TokenHeader/TokenHeader.module.css deleted file mode 100644 index 2d9ef9600c..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/TokenHeader/TokenHeader.module.css +++ /dev/null @@ -1,57 +0,0 @@ -.token-header { - display: flex; - flex-direction: column; - width: 100%; - gap: 1rem; -} - -.token-details { - width: 100%; - height: min-content; - border: 1px solid rgb(240, 236, 236); - border-radius: 10px; - gap: 3rem; - padding: 1rem 2rem; - display: flex; - justify-content: flex-start; - align-items: center; - background-color: rgb(247, 245, 245); -} - -.token-details div { - display: flex; - align-items: center; - gap: 1rem; -} - -.token-icon { - height: 100%; - transform: translateY(10%); - color: rgb(34, 70, 70); -} - -.token-account { - font-size: 16px; - width: 100%; - height: min-content; - display: flex; - align-items: center; - justify-content: center; - background-color: rgb(247, 245, 245); - border-radius: 10px; - padding: 1rem; - padding-left: 2rem; -} - -.token-account span { - display: flex; - align-items: center; - gap: .5rem; -} - -.token-account-icon { - color: rgb(22, 92, 65); - font-size: 28px; - height: 30px; - width: 30px; -} \ No newline at end of file diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/TokenHeader/TokenHeader.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/TokenHeader/TokenHeader.tsx deleted file mode 100644 index c7df8416cb..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/TokenHeader/TokenHeader.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import TokenAccount from "./TokenAccount"; -import styles from "./TokenHeader.module.css"; -import { ethTokenDetails } from "../../queries"; -import { useQuery } from "@tanstack/react-query"; -import { TokenMetadata20 } from "../../../../common/supabase-types"; - -function TokenHeader(props: { accountNum: string; tokenAddress: string }) { - const { isError, data, error } = useQuery( - ethTokenDetails("erc20", props.tokenAddress), - ); - - if (isError) { - console.error("Token header fetch error:", error); - } - - console.log(data); - - return ( -
- -
-

- Address: {props.tokenAddress} -

-

- Created at: {data?.created_at} -

-

- Total supply: - {(data as TokenMetadata20)?.total_supply} -

-
-
- ); -} - -export default TokenHeader; diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/TransactionList/TransactionList.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/TransactionList/TransactionList.tsx index 9bdb9f51c1..b53e86c455 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/TransactionList/TransactionList.tsx +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/TransactionList/TransactionList.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { ethereumAllTransactionsQuery } from "../../queries"; +import { UseQueryOptions } from "@tanstack/react-query"; import { transactionColumnsConfig } from "./transactionColumnsConfig"; import type { UITableListingPaginationActionProps } from "../../../../components/ui/UITableListing/UITableListingPaginationAction"; import UITableListing from "../../../../components/ui/UITableListing/UITableListing"; @@ -12,7 +12,11 @@ export type TransactionListColumn = keyof typeof transactionColumnsConfig; /** * TransactionList properties. */ -export interface TransactionListProps { +export interface TransactionListProps { + queryFunction: ( + page: number, + pageSize: number, + ) => UseQueryOptions; footerComponent: React.ComponentType; columns: TransactionListColumn[]; rowsPerPage: number; @@ -26,15 +30,21 @@ export interface TransactionListProps { * @param columns list of columns to be rendered. * @param rowsPerPage how many rows to show per page. */ -const TransactionList: React.FC = ({ + +export default function TransactionList< + T extends { + [key: string]: any; + }, +>({ + queryFunction, footerComponent, columns, rowsPerPage, tableSize, -}) => { +}: TransactionListProps) { return ( = ({ tableSize={tableSize} /> ); -}; - -export default TransactionList; +} diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/TransactionList/transactionColumnsConfig.ts b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/TransactionList/transactionColumnsConfig.ts index a37590a6b9..b687ec047b 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/TransactionList/transactionColumnsConfig.ts +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/components/TransactionList/transactionColumnsConfig.ts @@ -24,7 +24,7 @@ export const transactionColumnsConfig = { isLongString: true, }, value: { - name: "Value", + name: "Eth Value", field: "eth_value", }, method: { diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/index.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/index.tsx index 6eec88fbb7..026aa83122 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/index.tsx +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/index.tsx @@ -1,15 +1,8 @@ -import { Outlet } from "react-router-dom"; -import TokenDetails from "./pages/Details/TokenDetails"; +import { AppConfig } from "../../common/types/app"; import Dashboard from "./pages/Dashboard/Dashboard"; import Blocks from "./pages/Blocks/Blocks"; import Transactions from "./pages/Transactions/Transactions"; import Accounts from "./pages/Accounts/Accounts"; -import TransactionDetails from "./pages/Details/TransactionDetails"; -import ERC20 from "./pages/ERC20/ERC20"; -import SingleTokenHistory from "./pages/SingleTokenHistory/SingleTokenHistory"; -import ERC721 from "./pages/ERC721/ERC721"; -import BlockDetails from "./pages/Details/BlockDetails"; -import { AppConfig } from "../../common/types/app"; const ethConfig: AppConfig = { name: "Ethereum", @@ -20,12 +13,8 @@ const ethConfig: AppConfig = { url: "/", }, { - title: "ERC20", - url: "/accounts/erc20", - }, - { - title: "ERC721 (NFT)", - url: "/accounts/erc721", + title: "Accounts", + url: "/accounts", }, ], routes: [ @@ -42,51 +31,7 @@ const ethConfig: AppConfig = { }, { path: "accounts", - element: , - children: [ - { - path: ":standard", - element: , - }, - ], - }, - { - path: "token-details", - element: , - children: [ - { - path: ":standard/:address", - element: ( -
- -
- ), - }, - ], - }, - { - path: "erc20", - element: , - children: [ - { - path: ":account", - element: , - }, - { - path: "trend/:account/:address", - element: , - }, - ], - }, - { - path: "erc721", - element: , - children: [ - { - path: ":account", - element: , - }, - ], + element: , }, ], }; diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Accounts/AccountTokenList.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Accounts/AccountTokenList.tsx new file mode 100644 index 0000000000..4ca44ae4e5 --- /dev/null +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Accounts/AccountTokenList.tsx @@ -0,0 +1,58 @@ +import * as React from "react"; +import Box from "@mui/material/Box"; +import Tab from "@mui/material/Tab"; +import TabContext from "@mui/lab/TabContext"; +import TabList from "@mui/lab/TabList"; +import TabPanel from "@mui/lab/TabPanel"; + +import PageTitle from "../../../../components/ui/PageTitle"; +import AccountERC721View from "../../components/AccountERC721View/AccountERC721View"; +import AccountERC20View from "../../components/AccountERC20View/AccountERC20View"; + +const ERC20_TAB_INDEX = "erc20"; +const ERC721_TAB_INDEX = "erc721"; + +export type AccountTokenListProps = { + accountAddress: string; +}; + +export default function AccountTokenList({ + accountAddress, +}: AccountTokenListProps) { + const [tabIndex, setTabIndex] = React.useState(ERC20_TAB_INDEX); + + return ( + + Tokens + + + + { + setTabIndex(newValue); + }} + aria-label="tab select token to display" + textColor="secondary" + indicatorColor="primary" + > + + + + + + + + + + + + + + ); +} diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Accounts/AccountTransactionList.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Accounts/AccountTransactionList.tsx new file mode 100644 index 0000000000..f142df7e4a --- /dev/null +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Accounts/AccountTransactionList.tsx @@ -0,0 +1,29 @@ +import Box from "@mui/material/Box"; + +import PageTitle from "../../../../components/ui/PageTitle"; +import TransactionList from "../../components/TransactionList/TransactionList"; +import { ethAccountTransactionsQuery } from "../../queries"; +import UITableListingPaginationAction from "../../../../components/ui/UITableListing/UITableListingPaginationAction"; + +export type AccountTransactionListProps = { + accountAddress: string; +}; + +export default function AccountTransactionList({ + accountAddress, +}: AccountTransactionListProps) { + return ( + + Transactions + + ethAccountTransactionsQuery(page, pageSize, accountAddress) + } + footerComponent={UITableListingPaginationAction} + columns={["hash", "from", "to", "value", "method"]} + rowsPerPage={7} + tableSize="small" + /> + + ); +} diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Accounts/Accounts.module.css b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Accounts/Accounts.module.css deleted file mode 100644 index 43c52de4a5..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Accounts/Accounts.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.accounts-wrapper{ - width:100%; - display:grid; - grid-template-columns: repeat(auto-fit, 35rem); - gap: 1rem; -} \ No newline at end of file diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Accounts/Accounts.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Accounts/Accounts.tsx index e8254d8ecb..e3f35a8f00 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Accounts/Accounts.tsx +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Accounts/Accounts.tsx @@ -1,50 +1,69 @@ -import CardWrapper from "../../../../components/ui/CardWrapper"; -import { useNavigate, useParams } from "react-router-dom"; -import { useState } from "react"; -import { useQuery } from "@tanstack/react-query"; -import { ethGetTokenOwners } from "../../queries"; +import * as React from "react"; +import { ethers } from "ethers"; +import SearchIcon from "@mui/icons-material/Search"; +import TextField from "@mui/material/TextField"; +import Button from "@mui/material/Button"; +import Box from "@mui/material/Box"; -function Accounts() { - const params = useParams(); - if (typeof params.standard === "undefined") { - throw new Error(`Accounts called with empty token standard ${params}`); - } - const navigate = useNavigate(); - const { isError, data, error } = useQuery( - ethGetTokenOwners(params.standard.toLowerCase()), - ); - const [searchKey, setSearchKey] = useState(""); +import AccountTokenList from "./AccountTokenList"; +import AccountTransactionList from "./AccountTransactionList"; + +export default function Accounts() { + const [accountSearchText, setAccountSearchText] = React.useState(""); + const [errorText, setErrorText] = React.useState(""); + const [account, setAccount] = React.useState(""); - if (isError) { - console.error("Token owners fetch error:", error); - } + const handleSearchClick = () => { + if (!ethers.isAddress(accountSearchText.toLowerCase())) { + return setErrorText( + "Address format not recognized, use valid hexadecimal address", + ); + } - const tableProps = { - onClick: { - action: (param: string) => navigate(`/eth/${params.standard}/${param}`), - prop: "address", - }, - schema: [ - { - display: "Account address", - objProp: ["address"], - }, - ], + setAccount(accountSearchText); }; return ( -
- setSearchKey(e)} - > -
+ + {/* Search Bar */} + + + { + setErrorText(""); + setAccountSearchText(e.target.value.trim()); + }} + error={Boolean(errorText)} + helperText={errorText} + /> + + + + + {/* Account transactions */} + + {account && } + + + {/* Account token list */} + {account && } + ); } - -export default Accounts; diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Dashboard/TransactionSummary.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Dashboard/TransactionSummary.tsx index 9b923b9623..883a29a06a 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Dashboard/TransactionSummary.tsx +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Dashboard/TransactionSummary.tsx @@ -2,6 +2,7 @@ import { Link as RouterLink } from "react-router-dom"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import TransactionList from "../../components/TransactionList/TransactionList"; +import { ethAllTransactionsQuery } from "../../queries"; function TransactionListViewAllAction() { return ( @@ -22,6 +23,7 @@ function TransactionListViewAllAction() { export default function TransactionSummary() { return ( -
- {isSuccess ? ( - <> -

Block Details

-

- Address: {data.number}{" "} -

-

- {" "} - Created at: - {data.created_at} -

-

- Hash: - {data.hash} -

-

- Number of transaction: - {data.number_of_tx} -

-

- Sync at: - {data.sync_at} -

- - ) : ( -
Failed to load details
- )} -
- - ); -} - -export default BlockDetails; diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Details/Details.module.css b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Details/Details.module.css deleted file mode 100644 index 899ff99432..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Details/Details.module.css +++ /dev/null @@ -1,32 +0,0 @@ -h1{ - padding: 0; - margin: 0; - margin-bottom: .5rem; -} -.details { - display: flex; - gap: .75rem; -} -.details-card{ - display: flex; - flex-direction: column; - gap: 15px; - border: 1px solid rgb(230, 224, 224); - border-radius: 10px; - padding: 1.5rem 2rem; - width:45%; -} -span { - display: inline-block; - font-size: 1.1rem; -} - -@media (max-width: 1699px) { - .details{ - flex-direction: column; - } - - .details-card { - width: 100%; - } -} \ No newline at end of file diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Details/TokenDetails.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Details/TokenDetails.tsx deleted file mode 100644 index 4db7448293..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Details/TokenDetails.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { STANDARDS } from "../../../../common/token-standards"; -import styles from "./Details.module.css"; -import { useParams } from "react-router-dom"; -import { useQuery } from "@tanstack/react-query"; -import { ethTokenDetails } from "../../queries"; -import { TokenMetadata20 } from "../../../../common/supabase-types"; - -const TokenDetails = () => { - const params = useParams(); - if ( - typeof params.standard === "undefined" || - typeof params.address === "undefined" - ) { - throw new Error(`Token details called with empty args ${params}`); - } - const { isError, data, error } = useQuery( - ethTokenDetails(params.standard.toLowerCase(), params.address), - ); - - if (isError) { - console.error("Token details fetch error:", error); - } - - return ( -
-
-

Token Details

-

- Adress: {data?.address}{" "} -

-

- Created at: - {data?.created_at} -

-

- Name: - {data?.name} -

-

- Symbol: - {data?.symbol} -

- {params.standard === STANDARDS.erc20 && ( -

total_supply : {(data as TokenMetadata20).total_supply}

- )} -
-
- ); -}; - -export default TokenDetails; diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Details/TransactionDetails.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Details/TransactionDetails.tsx deleted file mode 100644 index 1a5a846f78..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Details/TransactionDetails.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import CardWrapper from "../../../../components/ui/CardWrapper"; -import styles from "./Details.module.css"; -import { useParams } from "react-router-dom"; -import { useQuery } from "@tanstack/react-query"; -import { ethereumTokenTransfersByTxId, ethereumTxById } from "../../queries"; - -const TransactionsDetails = () => { - const params = useParams(); - if (typeof params.id === "undefined") { - throw new Error(`TransactionsDetails called with empty txId ${params}`); - } - const { - isError: txIsError, - data: txData, - error: txError, - } = useQuery({ - ...ethereumTxById(params.id), - staleTime: Infinity, - }); - - const { - isError: txTransfersIsError, - data: txTransfersData, - error: txTransfersError, - } = useQuery({ - ...ethereumTokenTransfersByTxId(params.id), - staleTime: Infinity, - }); - - if (txIsError) { - console.error("Transaction fetch error:", txError); - } - if (txTransfersIsError) { - console.error("Token transfers fetch error:", txTransfersError); - } - - const detailsTableProps = { - onClick: { - action: () => {}, - prop: "id", - }, - schema: [ - { display: "transfer id", objProp: ["id"] }, - { display: "sender/recipient", objProp: ["sender", "recipient"] }, - { display: "value", objProp: ["value"] }, - ], - }; - - return ( -
-
-

Details of Transaction

-

- {" "} - Hash: {txData?.hash}{" "} -

-

- Block: - {txData?.block_number} -

-

- From: - {txData?.from} -

-

- To: - {txData?.to}{" "} -

-

- {" "} - Value:   {txData?.eth_value} -

-
- -
- ); -}; - -export default TransactionsDetails; diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/ERC20/ERC20.module.css b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/ERC20/ERC20.module.css deleted file mode 100644 index 92eaa56f56..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/ERC20/ERC20.module.css +++ /dev/null @@ -1,19 +0,0 @@ -.erc-content { - display: flex; - gap:2rem; -} - -.erc-wrap{ - width:100%; - display: flex; - gap:1rem; - flex-direction: column; - -} - - -@media (max-width: 1699px) { - .erc-content{ - flex-direction: column; - } -} \ No newline at end of file diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/ERC20/ERC20.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/ERC20/ERC20.tsx deleted file mode 100644 index cc187d674a..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/ERC20/ERC20.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import CardWrapper from "../../../../components/ui/CardWrapper"; -import Chart from "../../components/Chart/Chart"; -import styles from "./ERC20.module.css"; -import { useNavigate, useParams } from "react-router-dom"; -import { useQuery } from "@tanstack/react-query"; -import { ethERC20TokensByOwner } from "../../queries"; -import TokenAccount from "../../components/TokenHeader/TokenAccount"; - -const ERC20 = () => { - const params = useParams(); - if (typeof params.account === "undefined") { - throw new Error(`ERC20 called with empty account address ${params}`); - } - const navigate = useNavigate(); - const { isError, data, error } = useQuery( - ethERC20TokensByOwner(params.account), - ); - if (isError) { - console.error("Data fetch error:", error); - } - - const ercTableProps = { - onClick: { - action: (token_address: string) => - navigate(`/eth/erc20/trend/${params.account}/${token_address}`), - prop: "token_address", - }, - schema: [ - { - display: "token address", - objProp: ["token_address"], - }, - { - display: "balance", - objProp: ["balance"], - }, - ], - }; - - return ( -
-
- - -
-
- -
-
- ); -}; - -export default ERC20; diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/ERC721/ERC721.module.css b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/ERC721/ERC721.module.css deleted file mode 100644 index fb8d75ce96..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/ERC721/ERC721.module.css +++ /dev/null @@ -1,18 +0,0 @@ -.erc-content { - display: flex; - gap:2rem; -} - -.erc-wrap{ - display: flex; - gap:1rem; - flex-direction: column; - align-items: center; -} - -@media (max-width: 1699px) { - - .erc-content{ - flex-direction: column; - } -} \ No newline at end of file diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/ERC721/ERC721.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/ERC721/ERC721.tsx deleted file mode 100644 index 6360cd04ce..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/ERC721/ERC721.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import CardWrapper from "../../../../components/ui/CardWrapper"; - -import styles from "./ERC721.module.css"; -import { useNavigate, useParams } from "react-router-dom"; -import { useQuery } from "@tanstack/react-query"; -import { ethAllERC721History, ethERC721TokensByTxId } from "../../queries"; -import TokenAccount from "../../components/TokenHeader/TokenAccount"; - -function ERC721() { - const params = useParams(); - if (typeof params.account === "undefined") { - throw new Error(`ERC721 list called with empty account address ${params}`); - } - const navigate = useNavigate(); - const { - isError: isTokenListError, - data: tokenList, - error: tokenListError, - } = useQuery(ethERC721TokensByTxId(params.account)); - const { - isError: isTokenMetadataError, - data: tokenMetadata, - error: tokenMetadataError, - } = useQuery(ethAllERC721History()); - - if (isTokenListError) { - console.error("Token list for account fetch error:", tokenListError); - } - - if (isTokenMetadataError) { - console.error("Token metadata fetch error:", tokenMetadataError); - } - - const ercTableProps = { - onClick: { - action: (param: string) => navigate(`/eth/token-details/erc721/${param}`), - prop: "token_address", - }, - schema: [ - { - display: "symbol", - objProp: ["symbol"], - }, - { - display: "URI", - objProp: ["uri"], - }, - ], - }; - const metaProps = { - onClick: { - action: () => {}, - prop: "", - }, - schema: [ - { - display: "created at", - objProp: ["created_at"], - }, - { - display: "sender/recipient", - objProp: ["sender", "recipient"], - }, - { - display: "token address", - objProp: ["token_address"], - }, - ], - }; - - return ( -
- -
- - -
-
- ); -} - -export default ERC721; diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/SingleTokenHistory/SingleTokenHistory.module.css b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/SingleTokenHistory/SingleTokenHistory.module.css deleted file mode 100644 index aa59d7de7e..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/SingleTokenHistory/SingleTokenHistory.module.css +++ /dev/null @@ -1,12 +0,0 @@ -.token-history { - display: flex; - flex-direction: column; - gap: 1rem; -} - -.transactions { - display: flex; - flex-direction: column; - gap: 2rem; - margin-top: 2rem; -} \ No newline at end of file diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/SingleTokenHistory/SingleTokenHistory.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/SingleTokenHistory/SingleTokenHistory.tsx deleted file mode 100644 index d052681251..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/SingleTokenHistory/SingleTokenHistory.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import CardWrapper from "../../../../components/ui/CardWrapper"; -import LineChart from "../../components/Chart/LineChart"; -import styles from "./SingleTokenHistory.module.css"; -import EmptyTablePlaceholder from "../../../../components/ui/EmptyTablePlaceholder/EmptyTablePlaceholder"; -import { useParams } from "react-router-dom"; -import { useQuery } from "@tanstack/react-query"; -import { ethERC20TokensHistory } from "../../queries"; -import TokenHeader from "../../components/TokenHeader/TokenHeader"; - -type ObjectKey = keyof typeof styles; - -const SingleTokenHistory = () => { - const params = useParams(); - if ( - typeof params.address === "undefined" || - typeof params.account === "undefined" - ) { - throw new Error(`ERC20 called with empty token or owner address ${params}`); - } - const { - isError, - data: txData, - error, - } = useQuery(ethERC20TokensHistory(params.address, params.account)); - - const { - data: balanceHistory, - isError: isBalanceHistoryError, - error: balanceHistoryError, - } = useQuery({ - queryKey: ["balanceHistory", txData], - queryFn: () => { - let balance = 0; - const balances = (txData ?? []).map((txn) => { - let txn_value = txn.value || 0; - if (txn.recipient !== params.account) { - txn_value *= -1; - } - balance += txn_value; - return { - created_at: txn.created_at + "Z", - balance: balance, - }; - }); - return balances; - }, - enabled: !!txData, - }); - - if (isError) { - console.error("Token history fetch error:", error); - } - - if (isBalanceHistoryError) { - console.error("Balance history calculation error:", balanceHistoryError); - } - - const tokenTableProps = { - schema: [ - { - display: "created at", - objProp: ["created_at"], - }, - { - display: "transaction hash", - objProp: ["transaction_hash"], - }, - { - display: "sender/recipient", - objProp: ["sender", "recipient"], - }, - { - display: "token address", - objProp: ["token_address"], - }, - { - display: "token value", - objProp: ["value"], - }, - ], - }; - - return ( -
- -
- {txData && txData.length > 0 ? ( - <> - - - - ) : ( - - )} -
-
- ); -}; - -export default SingleTokenHistory; diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Transactions/Transactions.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Transactions/Transactions.tsx index 36f12a337e..073e1403b4 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Transactions/Transactions.tsx +++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/pages/Transactions/Transactions.tsx @@ -2,12 +2,14 @@ import Box from "@mui/material/Box"; import TransactionList from "../../components/TransactionList/TransactionList"; import PageTitleWithGoBack from "../../../../components/ui/PageTitleWithGoBack"; import UITableListingPaginationAction from "../../../../components/ui/UITableListing/UITableListingPaginationAction"; +import { ethAllTransactionsQuery } from "../../queries"; export default function Transactions() { return ( Transactions { @@ -48,98 +50,92 @@ export function ethereumAllTransactionsQuery(page: number, pageSize: number) { }); } -// todo - refactor to single get-all query with paging -export function ethereumAllBlocksQuery(page: number, pageSize: number) { +/** + * Get all recorded ethereum transactions involving account `accountAddress` + * (either as sender or recipient). + * Returns `queryOptions` to be used as argument to `useQuery` from `react-query`. + * Supports paging. + */ +export function ethAccountTransactionsQuery( + page: number, + pageSize: number, + accountAddress: string, +) { const fromIndex = page * pageSize; const toIndex = fromIndex + pageSize - 1; - const tableName = "block"; + const tableName = "transaction"; + return queryOptions({ queryKey: [supabaseQueryKey, createQueryKey(tableName, { page, pageSize })], queryFn: async () => { const { data, error } = await supabase .from(tableName) .select() - .order("number", { ascending: false }) + .or(`from.eq.${accountAddress}, to.eq.${accountAddress}`) + .order("block_number", { ascending: false }) .range(fromIndex, toIndex); if (error) { throw new Error( - `Could not get data from '${tableName}' table: ${error.message}`, + `Could not get data from '${tableName}' table for account '${accountAddress}: ${error.message}`, ); } - return data as Block[]; + return data as Transaction[]; }, }); } -export function ethereumBlockByNumber(blockNumber: number | string) { - return supabaseQuerySingleMatchingEntry("block", { - number: blockNumber, - }); -} - -export function ethereumTxById(txId: number | string) { - return supabaseQuerySingleMatchingEntry("transaction", { - id: txId, - }); -} - -export function ethereumTokenTransfersByTxId(txId: number | string) { - return supabaseQueryAllMatchingEntries("token_transfer", { - transaction_id: txId, - }); -} - -export function ethGetTokenOwners(tokenStandard: string) { - if (!["erc20", "erc721"].includes(tokenStandard)) { - throw new Error(`Unknown token standard requested! ${tokenStandard}`); - } - const tableName = `token_${tokenStandard}`; +/** + * Get all recorded ethereum blocks. + * Returns `queryOptions` to be used as argument to `useQuery` from `react-query`. + * Supports paging. + */ +export function ethAllBlocksQuery(page: number, pageSize: number) { + const fromIndex = page * pageSize; + const toIndex = fromIndex + pageSize - 1; + const tableName = "block"; return queryOptions({ - queryKey: [supabaseQueryKey, tableName, "account_address"], + queryKey: [supabaseQueryKey, createQueryKey(tableName, { page, pageSize })], queryFn: async () => { const { data, error } = await supabase .from(tableName) - .select("account_address"); + .select() + .order("number", { ascending: false }) + .range(fromIndex, toIndex); + if (error) { throw new Error( - `Could not get token owners from '${tableName}' table: ${error.message}`, + `Could not get data from '${tableName}' table: ${error.message}`, ); } - // TODO - use stored procedure to return unique account list - return [...new Set(data.map((el) => el.account_address))].map((el) => ({ - address: el, - })); + return data as Block[]; }, }); } -export function ethERC20TokensByOwner(accountAddress: number | string) { - return supabaseQueryAllMatchingEntries("token_erc20", { - account_address: accountAddress, - }); -} - -export function ethERC20TokensHistory( +/** + * Get history of ERC20 transaction on token `tokenAddress` involving account `accountAddress` + * (either as sender or recipient). + * Returns `queryOptions` to be used as argument to `useQuery` from `react-query`. + */ +export function ethERC20TokenHistory( tokenAddress: string, - tokenOwnerAddress: string, + accountAddress: string, ) { const tableName = "erc20_token_history_view"; return queryOptions({ - queryKey: [supabaseQueryKey, tableName, tokenAddress, tokenOwnerAddress], + queryKey: [supabaseQueryKey, tableName, tokenAddress, accountAddress], queryFn: async () => { const { data, error } = await supabase .from(tableName) .select() .match({ token_address: tokenAddress }) - .or( - `sender.eq.${tokenOwnerAddress}, recipient.eq.${tokenOwnerAddress}`, - ); + .or(`sender.eq.${accountAddress}, recipient.eq.${accountAddress}`); if (error) { throw new Error( - `Could not get ERC20 [${tokenAddress}] of user ${tokenOwnerAddress} token history: ${error.message}`, + `Could not get ERC20 [${tokenAddress}] of user ${accountAddress} token history: ${error.message}`, ); } @@ -148,27 +144,60 @@ export function ethERC20TokensHistory( }); } -export function ethERC721TokensByTxId(accountAddress: string) { - return supabaseQueryAllMatchingEntries("erc721_txn_meta_view", { - account_address: accountAddress, - }); +export interface EthAllERC721TokensByAccountResponseType { + token_id: number; + uri: string; + account_address: string; + last_owner_change: string; + token_metadata_erc721: TokenMetadata721; } -export function ethAllERC721History() { - return supabaseQueryTable("erc721_token_history_view"); +/** + * Get list of all ERC721 tokens belonging to `accountAddress` + * Returns `queryOptions` to be used as argument to `useQuery` from `react-query`. + */ +export function ethAllERC721TokensByAccount(accountAddress: string) { + return queryOptions({ + queryKey: [supabaseQueryKey, "ethAllERC721TokensByAccount", accountAddress], + queryFn: async () => { + const { data, error } = await supabase + .from("token_erc721") + .select( + "token_id, uri, account_address, last_owner_change, token_metadata_erc721(*)", + ) + .eq("account_address", accountAddress); + + if (error) { + throw new Error( + `Could not ${accountAddress} ERC721 token list: ${error.message}`, + ); + } + + return data as EthAllERC721TokensByAccountResponseType[]; + }, + }); } -export function ethTokenDetails(tokenStandard: string, tokenAddress: string) { - if (!["erc20", "erc721"].includes(tokenStandard)) { - throw new Error(`Unknown token standard requested! ${tokenStandard}`); - } +/** + * Get list of all ERC20 tokens belonging to `accountAddress` + * Returns `queryOptions` to be used as argument to `useQuery` from `react-query`. + */ +export function ethAllERC20TokensByAccount(accountAddress: string) { + return queryOptions({ + queryKey: [supabaseQueryKey, "ethAllERC20TokensByAccount", accountAddress], + queryFn: async () => { + const { data, error } = await supabase + .from("token_erc20") + .select() + .eq("account_address", accountAddress); - const tableName = `token_metadata_${tokenStandard}`; + if (error) { + throw new Error( + `Could not ${accountAddress} ERC20 token list: ${error.message}`, + ); + } - return supabaseQuerySingleMatchingEntry( - tableName, - { - address: tokenAddress, + return data as TokenERC20[]; }, - ); + }); } diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/static/nft-placeholder.png b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/static/nft-placeholder.png new file mode 100644 index 0000000000..0ee05491cf Binary files /dev/null and b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/static/nft-placeholder.png differ diff --git a/packages/cacti-ledger-browser/src/main/typescript/common/supabase-types.ts b/packages/cacti-ledger-browser/src/main/typescript/common/supabase-types.ts index 45b608e68e..373e985e86 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/common/supabase-types.ts +++ b/packages/cacti-ledger-browser/src/main/typescript/common/supabase-types.ts @@ -61,19 +61,19 @@ export interface Transaction { } export interface TokenHistoryItem { - transaction_hash: string | null; - token_address: string | null; - created_at: string | null; - sender: string | null; - recipient: string | null; + transaction_hash: string; + token_address: string; + created_at: string; + sender: string; + recipient: string; } export interface TokenHistoryItem721 extends TokenHistoryItem { - token_id: number | null; + token_id: number; } export interface TokenHistoryItem20 extends TokenHistoryItem { - value: number | null; + value: number; } export interface TokenTransactionMetadata721 { @@ -97,7 +97,14 @@ export interface TableProps { schema: TableProperty[]; } -export interface balanceDate { - created_at: string; +/// MANUAL EDITS + +// Materialized View +export interface TokenERC20 { + account_address: string; balance: number; + name: string; + symbol: string; + total_supply: number; + token_address: string; } diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/AccountCard/AccountCard.module.css b/packages/cacti-ledger-browser/src/main/typescript/components/AccountCard/AccountCard.module.css deleted file mode 100644 index 45da5dea56..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/components/AccountCard/AccountCard.module.css +++ /dev/null @@ -1,19 +0,0 @@ -.card { - display: flex; - justify-content: center; - background-color: rgb(252, 249, 249); - align-items: center; - height: 5rem; - padding: 0 2rem; - margin-top: 5px; - border-radius: 10px; - border: .5px solid rgb(224, 228, 224); - width: 35rem; - font-size: 18px; -} - -.card:hover { - cursor: pointer; - background-color: #ffffff; - border:1px solid rgb(39, 153, 39); -} \ No newline at end of file diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/AccountCard/AccountCard.tsx b/packages/cacti-ledger-browser/src/main/typescript/components/AccountCard/AccountCard.tsx deleted file mode 100644 index 72a6075872..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/components/AccountCard/AccountCard.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useNavigate, useParams } from "react-router-dom"; -import styles from "./AccountCard.module.css"; - -function AccountCard(props: any) { - const params = useParams(); - const navigate = useNavigate(); - const handleClick = () => { - navigate(`/${params.standard}/${props.address}`); - }; - return ( -
- {props.address} -
- ); -} - -export default AccountCard; diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/BlockCard/BlockCard.module.css b/packages/cacti-ledger-browser/src/main/typescript/components/BlockCard/BlockCard.module.css deleted file mode 100644 index 3e90ee76fa..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/components/BlockCard/BlockCard.module.css +++ /dev/null @@ -1,33 +0,0 @@ -.block-card { - display: flex; - gap: 1rem; - background-color: rgb(252, 249, 249); - height: 5rem; - align-items: center; - justify-content: space-around; - width: 100%; - padding: 1rem 0rem; - margin-top: 5px; - border-radius: 10px; - border: .5px solid rgb(242, 245, 242); - max-height: 100vh; - font-size: 14px; -} - -.block-card:hover { - cursor: pointer; - background-color: #ffffff; -} - -.block-num { - color: rgb(12, 105, 12); -} - -.block-hash { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - width: min-content; - max-width: 50%; -} \ No newline at end of file diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/BlockCard/BlockCard.tsx b/packages/cacti-ledger-browser/src/main/typescript/components/BlockCard/BlockCard.tsx deleted file mode 100644 index 88f07620fe..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/components/BlockCard/BlockCard.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import TagIcon from "@mui/icons-material/Tag"; - -import styles from "./BlockCard.module.css"; -import { useNavigate } from "react-router-dom"; - -function BlockCard(props: any) { - const navigate = useNavigate(); - const handleClick = () => { - navigate(`/blockDetails/${props.number}`); - }; - - return ( -
-

{props.created_at.toLocaleString()}

-

{props.number}

-

- {props.hash} -

-
- ); -} - -export default BlockCard; diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/ui/ShortHash.tsx b/packages/cacti-ledger-browser/src/main/typescript/components/ui/ShortHash.tsx deleted file mode 100644 index 72d75c853a..0000000000 --- a/packages/cacti-ledger-browser/src/main/typescript/components/ui/ShortHash.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import Tooltip from "@mui/material/Tooltip"; -import Typography, { TypographyProps } from "@mui/material/Typography"; - -const defaultMaxHashLength = 14; - -type ShortHashProps = { - hash: string; - maxLength?: number; -} & TypographyProps; - -/** - * Wrapper around MUI Typography for displaying shortified hash when necessary. - * Full hash will be shown in tooltip on hover. - * @param hash hash to be displayed. - * @param maxLength? maximum hash length (defualt is 14 - * @param TypographyProps? any additional props will be passed as Typography props - * - * @returns Short hash Typography with tooltip - */ -export default function ShortHash(params: ShortHashProps) { - const { hash, maxLength: inputMaxLength, ...typographyParams } = params; - const maxLength = inputMaxLength ? inputMaxLength : defaultMaxHashLength; - - if (hash.length <= maxLength) { - return {hash}; - } - - const shortHash = `...${hash.slice(-maxLength)}`; - return ( - - {shortHash} - - ); -} diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/ui/ShortenedLink.tsx b/packages/cacti-ledger-browser/src/main/typescript/components/ui/ShortenedLink.tsx new file mode 100644 index 0000000000..b7fed84671 --- /dev/null +++ b/packages/cacti-ledger-browser/src/main/typescript/components/ui/ShortenedLink.tsx @@ -0,0 +1,45 @@ +import Link, { LinkProps } from "@mui/material/Link"; +import Tooltip from "@mui/material/Tooltip"; + +const defaultMaxWidth = 200; + +export type ShortenedLinkProps = { + href: string; + maxWidth?: number | string; + direction?: "ltr" | "rtl"; +} & LinkProps; + +/** + * Wrapper around MUI Link for displaying shortified link when necessary. + * Full URL will be shown in tooltip on hover. + * Accepts all regular Typography props. Does not accept children element (displays the URL). + * + * @todo Use common code with ShortenedTypography. + * + * @param href Link to shortify. + * @param maxWidth Maximum with of the text to display before the shortening. + * @param width Same as `maxWidth` (overwrites it). + * @param direction `ltr` (default) will display the beginning of the link, `rtl` will display the ending. + */ +export default function ShortenedLink(params: ShortenedLinkProps) { + const { href, maxWidth: inputMaxWidth, direction, ...linkParams } = params; + const maxWidth = inputMaxWidth ? inputMaxWidth : defaultMaxWidth; + + return ( + + + {href} + + + ); +} diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/ui/ShortenedTypography.tsx b/packages/cacti-ledger-browser/src/main/typescript/components/ui/ShortenedTypography.tsx new file mode 100644 index 0000000000..6ca2b38b72 --- /dev/null +++ b/packages/cacti-ledger-browser/src/main/typescript/components/ui/ShortenedTypography.tsx @@ -0,0 +1,47 @@ +import Tooltip from "@mui/material/Tooltip"; +import Typography, { TypographyProps } from "@mui/material/Typography"; + +const defaultMaxWidth = 200; + +export type ShortenedTypographyProps = { + text: string; + maxWidth?: number | string; + direction?: "ltr" | "rtl"; +} & TypographyProps; + +/** + * Wrapper around MUI Typography for displaying shortified text when necessary. + * Full text will be shown in tooltip on hover. + * Accepts all regular Typography props. Does not accept children element (text is displayed in a tooltip) + * + * @param text Text to shortify. + * @param maxWidth Maximum with of the text to display before the shortening. + * @param width Same as `maxWidth` (overwrites it). + * @param direction `ltr` (default) will display the beginning of the text, `rtl` will display the ending. + */ +export default function ShortenedTypography(params: ShortenedTypographyProps) { + const { + text, + maxWidth: inputMaxWidth, + direction, + ...typographyParams + } = params; + const maxWidth = inputMaxWidth ? inputMaxWidth : defaultMaxWidth; + + return ( + + + {text} + + + ); +} diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/ui/UITableListing/UITableListing.tsx b/packages/cacti-ledger-browser/src/main/typescript/components/ui/UITableListing/UITableListing.tsx index 0c6fdaba15..1ec138d12b 100644 --- a/packages/cacti-ledger-browser/src/main/typescript/components/ui/UITableListing/UITableListing.tsx +++ b/packages/cacti-ledger-browser/src/main/typescript/components/ui/UITableListing/UITableListing.tsx @@ -14,7 +14,7 @@ import TableHead from "@mui/material/TableHead"; import CircularProgress from "@mui/material/CircularProgress"; import { useNotification } from "../../../common/context/NotificationContext"; -import ShortHash from "../ShortHash"; +import ShortenedTypography from "../ShortenedTypography"; import { StyledTableCell, StyledTableCellHeader, @@ -74,7 +74,7 @@ function getKeyField(columnConfig: ColumnConfigType) { function formatCellValue(config: ColumnConfigEntry, value: any) { if (config.isLongString) { - return ; + return ; } else if (config.isDate) { const date = new Date(value); return date.toLocaleString(); diff --git a/packages/cactus-plugin-persistence-ethereum/src/main/sql/schema.sql b/packages/cactus-plugin-persistence-ethereum/src/main/sql/schema.sql index 5b8a0535ce..f57f7b4ab0 100644 --- a/packages/cactus-plugin-persistence-ethereum/src/main/sql/schema.sql +++ b/packages/cactus-plugin-persistence-ethereum/src/main/sql/schema.sql @@ -303,7 +303,15 @@ GRANT ALL ON TABLE public.erc721_txn_meta_view TO service_role; CREATE MATERIALIZED VIEW IF NOT EXISTS public.token_erc20 TABLESPACE pg_default AS - SELECT balances.account_address, +SELECT + balances.account_address, + balances.balance, + balances.token_address, + metadata.name, + metadata.symbol, + metadata.total_supply +FROM + (SELECT balances.account_address, balances.token_address, sum(balances.balance) AS balance FROM ( SELECT erc20_token_history_view.recipient AS account_address, @@ -318,9 +326,26 @@ AS FROM erc20_token_history_view GROUP BY erc20_token_history_view.token_address, erc20_token_history_view.sender) balances GROUP BY balances.token_address, balances.account_address - HAVING sum(balances.balance) >= 0::numeric + HAVING sum(balances.balance) >= 0::numeric) AS balances + JOIN token_metadata_erc20 AS metadata ON balances.token_address = metadata.address WITH DATA; +-- Refresh public.token_erc20 on new token transfers +CREATE OR REPLACE FUNCTION refresh_token_erc20() +RETURNS TRIGGER AS $$ +BEGIN + REFRESH MATERIALIZED VIEW public.token_erc20; + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER refresh_token_erc20_on_token_transfers_trigger +AFTER INSERT OR UPDATE OR DELETE ON public.token_transfer +FOR EACH STATEMENT +EXECUTE FUNCTION refresh_token_erc20(); + +CREATE INDEX token_erc20_account_address_idx ON public.token_erc20(account_address); + ALTER TABLE IF EXISTS public.token_erc20 OWNER TO postgres; diff --git a/yarn.lock b/yarn.lock index a03585e724..c826720915 100644 --- a/yarn.lock +++ b/yarn.lock @@ -54,6 +54,13 @@ __metadata: languageName: node linkType: hard +"@adraffy/ens-normalize@npm:1.10.1": + version: 1.10.1 + resolution: "@adraffy/ens-normalize@npm:1.10.1" + checksum: 10/4cb938c4abb88a346d50cb0ea44243ab3574330c81d4f5aaaf9dfee584b96189d0faa404de0fcbef5a1b73909ea4ebc3e63d84bd23f9949e5c8d4085207a5091 + languageName: node + linkType: hard + "@adraffy/ens-normalize@npm:1.9.0": version: 1.9.0 resolution: "@adraffy/ens-normalize@npm:1.9.0" @@ -3959,7 +3966,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.23.8, @babel/runtime@npm:^7.23.9": +"@babel/runtime@npm:^7.23.9": version: 7.23.9 resolution: "@babel/runtime@npm:7.23.9" dependencies: @@ -7365,6 +7372,7 @@ __metadata: "@emotion/react": "npm:11.11.4" "@emotion/styled": "npm:11.11.5" "@mui/icons-material": "npm:5.15.10" + "@mui/lab": "npm:5.0.0-alpha.170" "@mui/material": "npm:5.15.15" "@supabase/supabase-js": "npm:1.35.6" "@tanstack/eslint-plugin-query": "npm:5.28.11" @@ -7372,17 +7380,13 @@ __metadata: "@tanstack/react-query-devtools": "npm:5.29.2" "@types/react": "npm:18.2.43" "@types/react-dom": "npm:18.2.17" - "@types/sort-by": "npm:1" "@vitejs/plugin-react": "npm:4.2.1" apexcharts: "npm:3.45.2" - localforage: "npm:1.10.0" - match-sorter: "npm:6.3.3" - moment: "npm:2.30.1" + ethers: "npm:6.12.1" react: "npm:18.2.0" react-apexcharts: "npm:1.4.1" react-dom: "npm:18.2.0" react-router-dom: "npm:6.21.3" - sort-by: "npm:1.2.0" typescript: "npm:5.2.2" vite: "npm:5.1.7" web3: "npm:4.1.1" @@ -10997,6 +11001,35 @@ __metadata: languageName: node linkType: hard +"@mui/lab@npm:5.0.0-alpha.170": + version: 5.0.0-alpha.170 + resolution: "@mui/lab@npm:5.0.0-alpha.170" + dependencies: + "@babel/runtime": "npm:^7.23.9" + "@mui/base": "npm:5.0.0-beta.40" + "@mui/system": "npm:^5.15.15" + "@mui/types": "npm:^7.2.14" + "@mui/utils": "npm:^5.15.14" + clsx: "npm:^2.1.0" + prop-types: "npm:^15.8.1" + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@mui/material": ">=5.15.0" + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + checksum: 10/be723d3824a8c56681c2fa71f3bfda5cad9404ed527ab82258ae85219fbb28e8080b90870954c064491699c15409fc2aa4e008e29011cadae1cb978179166f2d + languageName: node + linkType: hard + "@mui/material@npm:5.15.15": version: 5.15.15 resolution: "@mui/material@npm:5.15.15" @@ -15919,13 +15952,6 @@ __metadata: languageName: node linkType: hard -"@types/sort-by@npm:1": - version: 1.2.3 - resolution: "@types/sort-by@npm:1.2.3" - checksum: 10/edf61bad1538c5861e8187f45ea86476420cfd2e51e5d089e42eb21cf231afa91973e77e956069d85b4d67b21df4932ecd994062ca2155560db20b6a5ecea793 - languageName: node - linkType: hard - "@types/ssh2-streams@npm:*, @types/ssh2-streams@npm:0.1.9": version: 0.1.9 resolution: "@types/ssh2-streams@npm:0.1.9" @@ -26318,6 +26344,21 @@ __metadata: languageName: node linkType: hard +"ethers@npm:6.12.1": + version: 6.12.1 + resolution: "ethers@npm:6.12.1" + dependencies: + "@adraffy/ens-normalize": "npm:1.10.1" + "@noble/curves": "npm:1.2.0" + "@noble/hashes": "npm:1.3.2" + "@types/node": "npm:18.15.13" + aes-js: "npm:4.0.0-beta.5" + tslib: "npm:2.4.0" + ws: "npm:8.5.0" + checksum: 10/2995766164292b531499764d319d753b1f4e1cd7ddb4f26c6557a26e42947d5642a4b3bbeace0b8bb398b909dc22fafa4f52d5190a9bb8180bebf2dd3d4d48a9 + languageName: node + linkType: hard + "ethers@npm:6.3.0": version: 6.3.0 resolution: "ethers@npm:6.3.0" @@ -30523,13 +30564,6 @@ __metadata: languageName: node linkType: hard -"immediate@npm:~3.0.5": - version: 3.0.6 - resolution: "immediate@npm:3.0.6" - checksum: 10/f9b3486477555997657f70318cc8d3416159f208bec4cca3ff3442fd266bc23f50f0c9bd8547e1371a6b5e82b821ec9a7044a4f7b944798b25aa3cc6d5e63e62 - languageName: node - linkType: hard - "immediate@npm:~3.2.3": version: 3.2.3 resolution: "immediate@npm:3.2.3" @@ -35148,15 +35182,6 @@ __metadata: languageName: node linkType: hard -"lie@npm:3.1.1": - version: 3.1.1 - resolution: "lie@npm:3.1.1" - dependencies: - immediate: "npm:~3.0.5" - checksum: 10/c2c7d9dcc3a9aae641f41cde4e2e2cd571e4426b1f5915862781d77776672dcbca43461e16f4d382c9a300825c15e1a4923f1def3a5568d97577e077a3cecb44 - languageName: node - linkType: hard - "light-my-request@npm:^5.11.0": version: 5.12.0 resolution: "light-my-request@npm:5.12.0" @@ -35302,15 +35327,6 @@ __metadata: languageName: node linkType: hard -"localforage@npm:1.10.0": - version: 1.10.0 - resolution: "localforage@npm:1.10.0" - dependencies: - lie: "npm:3.1.1" - checksum: 10/d5c44be3a09169b013a3ebe252e678aaeb6938ffe72e9e12c199fd4307c1ec9d1a057ac2dfdfbb1379dfeec467a34ad0fc3ecd27489a2c43a154fb72b2822542 - languageName: node - linkType: hard - "locate-path@npm:^2.0.0": version: 2.0.0 resolution: "locate-path@npm:2.0.0" @@ -36170,16 +36186,6 @@ __metadata: languageName: node linkType: hard -"match-sorter@npm:6.3.3": - version: 6.3.3 - resolution: "match-sorter@npm:6.3.3" - dependencies: - "@babel/runtime": "npm:^7.23.8" - remove-accents: "npm:0.5.0" - checksum: 10/51c0e18983797f8995815ebddb3fd18fcd1a6d203d43c08dfe6f91c24b2d60119f85f020a5a4b58884f58d50a143f16d5201354bcfa93f8283a610f5715d880c - languageName: node - linkType: hard - "math-random@npm:^1.0.1": version: 1.0.4 resolution: "math-random@npm:1.0.4" @@ -37104,13 +37110,6 @@ __metadata: languageName: node linkType: hard -"moment@npm:2.30.1": - version: 2.30.1 - resolution: "moment@npm:2.30.1" - checksum: 10/ae42d876d4ec831ef66110bdc302c0657c664991e45cf2afffc4b0f6cd6d251dde11375c982a5c0564ccc0fa593fc564576ddceb8c8845e87c15f58aa6baca69 - languageName: node - linkType: hard - "morgan@npm:1.10.0": version: 1.10.0 resolution: "morgan@npm:1.10.0" @@ -38802,13 +38801,6 @@ __metadata: languageName: node linkType: hard -"object-path@npm:0.6.0": - version: 0.6.0 - resolution: "object-path@npm:0.6.0" - checksum: 10/ee9c2977f35630ef72d45a8718f4a5a1bf8576e56003dfea3fddb88a022351e41eb6592c7f4af2da8132d87cf38480c19d929f795e212eeec3314ea3375982cc - languageName: node - linkType: hard - "object-path@npm:^0.11.5": version: 0.11.8 resolution: "object-path@npm:0.11.8" @@ -43462,13 +43454,6 @@ __metadata: languageName: node linkType: hard -"remove-accents@npm:0.5.0": - version: 0.5.0 - resolution: "remove-accents@npm:0.5.0" - checksum: 10/4aa1a9d0c18468515a33c6760b0f8e28dfbceddcb846fac90b2189445445b27b11cc1df9fbceb97b4449438bc13250d77b27d4ab325b2d69933acc156d6c5b50 - languageName: node - linkType: hard - "remove-trailing-separator@npm:^1.0.1": version: 1.1.0 resolution: "remove-trailing-separator@npm:1.1.0" @@ -45703,15 +45688,6 @@ __metadata: languageName: node linkType: hard -"sort-by@npm:1.2.0": - version: 1.2.0 - resolution: "sort-by@npm:1.2.0" - dependencies: - object-path: "npm:0.6.0" - checksum: 10/82c9812aa318eff68669fe25cc0168d172ccbff9d34d52449c345631118b5e29608e3524e9028b23dc4959c8a7601b19a40ddbe3eb05234c0a54792912c5ad2f - languageName: node - linkType: hard - "sort-keys@npm:^5.0.0": version: 5.0.0 resolution: "sort-keys@npm:5.0.0"