Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Analytics - Assets secured by Wormhole #954

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/api/guardian-network/GuardianNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
GetOperationsInput,
GetOperationsOutput,
GetParsedVaaOutput,
GetSecuredTokensByWormholeOutput,
IChainActivity,
IChainActivityInput,
IProtocolActivity,
Expand Down Expand Up @@ -348,6 +349,12 @@ export class GuardianNetwork {
return payload || [];
}

async getSecuredTokensByWormhole(): Promise<GetSecuredTokensByWormholeOutput[]> {
return await this._client.doGet<GetSecuredTokensByWormholeOutput[]>(
"/wormhole/assets/secured-tokens",
);
}

private _vaaSearchCriteriaToPathSegmentFilter(
prefix: string,
criteria: {
Expand Down
12 changes: 12 additions & 0 deletions src/api/guardian-network/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,3 +443,15 @@ export interface GetOperationsOutput {
releaseTimestamp?: Date;
}
[];

export interface GetSecuredTokensByWormholeOutput {
symbol: string;
coingecko_id: string;
type: "PORTAL_TOKEN_BRIDGE" | "NATIVE_TOKEN_TRANSFER";
total_value_locked?: string; // only for PORTAL_TOKEN_BRIDGE
total_value_transferred: string;
fully_diluted_valuation?: string; // only for NATIVE_TOKEN_TRANSFER
platforms: {
[key: string]: string;
};
}
4 changes: 2 additions & 2 deletions src/components/atoms/Pagination/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const Pagination = ({
visiblePages = 5,
}: IProps) => {
const isFirstPage = currentPage === 1;
const isLastPage = currentPage === totalPages;
const isLastPage = currentPage >= totalPages;

return (
<div className={`pagination ${className}`} style={style}>
Expand Down Expand Up @@ -68,7 +68,7 @@ const Pagination = ({
<button
className="pagination-last-page"
onClick={goLastPage}
disabled={disabled || true || isLastPage}
disabled={disabled || isLastPage}
>
&gt;&gt;
</button>
Expand Down
19 changes: 17 additions & 2 deletions src/components/molecules/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,16 @@ const Header = ({
</NavigationMenu.Trigger>

<NavigationMenu.Content className="dropdown-menu-content">
<NavLink
to="/analytics/assets-secured-by-wormhole"
aria-label="analytics assets secured by Wormhole"
>
<ProtocolIcon protocol={NTT_APP_ID} width={24} /> Assets Secured
<div className="new-icon">NEW</div>
</NavLink>

<NavLink to="/analytics/ntt" aria-label="analytics Wormhole NTT">
<ProtocolIcon protocol={NTT_APP_ID} width={24} /> NTT
<div className="new-icon">NEW</div>
</NavLink>

<NavLink to="/analytics/tokens" aria-label="analytics tokens">
Expand Down Expand Up @@ -328,13 +335,21 @@ const Header = ({
showMobileNav && showMobileAnalytics ? "open" : ""
}`}
>
<NavLink
to="/analytics/assets-secured-by-wormhole"
aria-label="analytics assets secured by Wormhole"
onClick={closeAllMobileMenus}
>
<ProtocolIcon protocol={NTT_APP_ID} width={24} /> Assets Secured
<div className="new-icon">NEW</div>
</NavLink>

<NavLink
to="/analytics/ntt"
aria-label="analytics Wormhole NTT"
onClick={closeAllMobileMenus}
>
<ProtocolIcon protocol={NTT_APP_ID} width={24} /> NTT
<div className="new-icon">NEW</div>
</NavLink>

<NavLink
Expand Down
5 changes: 0 additions & 5 deletions src/components/molecules/Header/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@
position: relative;
width: 29px;

@include desktop {
position: absolute;
right: 8px;
}

&::before {
background-color: var(--color-plum);
bottom: -1.5px;
Expand Down
75 changes: 70 additions & 5 deletions src/components/organisms/Table/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CSSProperties, Fragment, useEffect, useState } from "react";
import { CSSProperties, Fragment, useCallback, useEffect, useState } from "react";
import { useSearchParams } from "react-router-dom";
import {
useTable,
Column,
Expand Down Expand Up @@ -27,6 +28,10 @@ type Props<T extends object> = {
numberOfRows?: number;
onRowClick?: (row: any) => void;
trackTxsSortBy?: boolean;
startIndex?: number;
endIndex?: number;
urlSorting?: boolean;
urlSortBy?: { id: string; desc: boolean };
};

type ExtendedTableInstance<T extends object> = TableInstance<T> & {
Expand All @@ -44,12 +49,17 @@ const Table = <T extends object>({
numberOfRows = 50,
onRowClick,
trackTxsSortBy = false,
startIndex,
endIndex,
urlSorting = false,
urlSortBy,
}: Props<T>) => {
const { environment } = useEnvironment();
const [openSortBy, setOpenSortBy] = useState(false);
const [currentSortBy, setCurrentSortBy] = useState(defaultSortBy);
const [currentSortBy, setCurrentSortBy] = useState(urlSortBy || defaultSortBy);
const { width } = useWindowSize();
const isDesktop = width >= BREAKPOINTS.desktop;
const [searchParams, setSearchParams] = useSearchParams();

const {
getTableProps,
Expand All @@ -62,12 +72,31 @@ const Table = <T extends object>({
{
columns,
data,
initialState: { sortBy: defaultSortBy ? [defaultSortBy] : [] } as Partial<TableState<T>>,
initialState: { sortBy: defaultSortBy ? [urlSortBy || defaultSortBy] : [] } as Partial<
TableState<T>
>,
disableSortRemove: true,
} as UseTableOptions<T> & UseSortByOptions<T>,
useSortBy,
);

const updateURL = useCallback(
(sortBy: string, desc: boolean) => {
setSearchParams(prevParams => {
const newParams = new URLSearchParams(prevParams);
newParams.set("sortBy", sortBy);
newParams.set("order", desc ? "desc" : "asc");
if (newParams.has("page")) {
newParams.set("page", "1");
}
return newParams;
});

setSortBy([{ id: sortBy, desc }]);
},
[setSearchParams, setSortBy],
);

useEffect(() => {
const sortedColumn = headerGroups.flatMap(group =>
// @ts-expect-error Property 'isSorted' exists at runtime but TypeScript doesn't know about it
Expand All @@ -84,13 +113,29 @@ const Table = <T extends object>({
}, [headerGroups]);

const handleReset = () => {
if (urlSorting) {
setSearchParams(prevParams => {
const newParams = new URLSearchParams(prevParams);
newParams.delete("sortBy");
newParams.delete("order");
newParams.set("page", "1");
return newParams;
});
}

setSortBy([defaultSortBy]);
setCurrentSortBy(defaultSortBy);

setOpenSortBy(false);
};

useLockBodyScroll({ isLocked: !isDesktop && openSortBy });

const visibleRows =
startIndex !== undefined || endIndex !== undefined
? rows.slice(startIndex ?? 0, endIndex ?? rows.length)
: rows;

return (
<>
{defaultSortBy && (
Expand Down Expand Up @@ -150,6 +195,7 @@ const Table = <T extends object>({
}));
// @ts-expect-error Property 'toggleSortBy' exists at runtime but TypeScript doesn't know about it
selectedColumn.toggleSortBy(currentSortBy.desc);
urlSorting && updateURL(selected.value, currentSortBy.desc);
}
}}
optionStyles={{ padding: 16 }}
Expand Down Expand Up @@ -181,6 +227,7 @@ const Table = <T extends object>({
if (column) {
// @ts-expect-error Property 'toggleSortBy' exists at runtime but TypeScript doesn't know about it
column.toggleSortBy(selected.value);
urlSorting && updateURL(currentSortBy.id, selected.value);
}
}}
optionStyles={{ padding: 16 }}
Expand Down Expand Up @@ -231,6 +278,14 @@ const Table = <T extends object>({
});
}, 0);
}

if (urlSorting) {
setTimeout(() => {
const sortedColumn = headerGroup.headers.find((header: any) => header.isSorted);
// @ts-expect-error Property 'isSortedDesc' exists at runtime but TypeScript doesn't know about it
updateURL(sortedColumn.id, sortedColumn.isSortedDesc);
}, 0);
}
}}
>
{headerGroup.headers.map((column: any, index) => {
Expand All @@ -246,7 +301,17 @@ const Table = <T extends object>({
return (
<th
key={index}
{...column.getHeaderProps(defaultSortBy ? column.getSortByToggleProps() : {})}
{...column.getHeaderProps(
defaultSortBy
? {
...column.getSortByToggleProps(),
onClick: (e: any) => {
e.preventDefault();
column.toggleSortBy(column.isSorted ? !column.isSortedDesc : true);
},
}
: {},
)}
style={{
...style,
color: column.isSorted ? "var(--color-white)" : "var(--color-gray-400)",
Expand Down Expand Up @@ -275,7 +340,7 @@ const Table = <T extends object>({
) : (
rows?.length > 0 && (
<tbody {...getTableBodyProps()}>
{rows.map((row, index) => {
{visibleRows.map((row, index) => {
prepareRow(row);
const justAppeared = (row?.original as any)?.justAppeared;
const txHash = (row?.original as any)?.txHashId;
Expand Down
2 changes: 2 additions & 0 deletions src/navigation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const VaaParser = lazy(() => import("../pages/VaaParser"));
const Submit = lazy(() => import("../pages/Submit"));
const Governor = lazy(() => import("../pages/Governor"));
const ApiDoc = lazy(() => import("../pages/ApiDoc"));
const AssetSecured = lazy(() => import("../pages/Analytics/AssetSecured"));
const NTT = lazy(() => import("../pages/Analytics/NTT"));
const NTTToken = lazy(() => import("../pages/Analytics/NTT/NTTToken"));
const Tokens = lazy(() => import("../pages/Analytics/Tokens"));
Expand Down Expand Up @@ -47,6 +48,7 @@ const Navigation = () => {
<Route path="/developers/vaa-parser" element={<VaaParser />} />
<Route path="/developers/vaa-parser/*" element={<VaaParser />} />
<Route path="/developers/api-doc" element={<ApiDoc />} />
<Route path="/analytics/assets-secured-by-wormhole" element={<AssetSecured />} />
<Route path="/analytics/ntt" element={<NTT />} />
<Route path="/analytics/ntt/:coingecko_id/:symbol" element={<NTTToken />} />
<Route path="/analytics/tokens" element={<Tokens />} />
Expand Down
93 changes: 93 additions & 0 deletions src/pages/Analytics/AssetSecured/ChainsSupported.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Chain, ChainId, chainToChainId } from "@wormhole-foundation/sdk";
import { useEnvironment } from "src/context/EnvironmentContext";
import { getChainName, getExplorerLink } from "src/utils/wormhole";
import { BlockchainIcon, Tooltip } from "src/components/atoms";
import { GetSecuredTokensByWormholeOutput } from "src/api/guardian-network/types";
import { chainNameMap, chainNameMapUnsupported } from "../NTT/NTTToken/Summary";
import "./styles.scss";

export const ChainsSupported = ({ item }: { item: GetSecuredTokensByWormholeOutput }) => {
const { environment } = useEnvironment();
const currentNetwork = environment.network;

return (
<Tooltip
type="info"
maxWidth={false}
tooltip={
<div className="asset-secured-list-table-item-tooltip">
{Object.entries(item.platforms).map(([chain, address], i) => {
const chainCapitalized = (chain.charAt(0).toUpperCase() + chain.slice(1)) as Chain;
const chainId = (chainToChainId(chainNameMap[chain] || chainCapitalized) ??
chainNameMapUnsupported[chain]) as ChainId;

return (
<a
href={getExplorerLink({
chainId: chainId,
network: currentNetwork,
value: address,
base: "token",
})}
className="asset-secured-list-table-item-tooltip-link"
rel="noreferrer"
target="_blank"
key={chain}
style={{
gridColumn: Object.entries(item.platforms).length === 1 ? "span 2" : "auto",
}}
>
<BlockchainIcon
background="#1F1F1F"
chainId={chainId}
network={currentNetwork}
size={20}
/>

<span className="asset-secured-list-table-item-tooltip-link-text">
{getChainName({
chainId: chainId,
network: currentNetwork,
})}
</span>
</a>
);
})}
</div>
}
>
<div className="asset-secured-list-table-item-chains">
{Object.entries(item.platforms).map(([chain, address], i) => {
const maxVisibleChains = 7;
const maxChainsLimit = 8;

if (i > maxVisibleChains) return null;

const chainCapitalized = (chain.charAt(0).toUpperCase() + chain.slice(1)) as Chain;
const chainId = (chainToChainId(chainNameMap[chain] || chainCapitalized) ??
chainNameMapUnsupported[chain]) as ChainId;

if (i === maxVisibleChains && Object.entries(item.platforms).length > maxChainsLimit) {
return (
<div key={chain} className="asset-secured-list-table-item-chains-more">
{Object.entries(item.platforms).length - maxVisibleChains}
</div>
);
}

return (
<BlockchainIcon
background="#1F1F1F"
chainId={chainId}
className="asset-secured-list-table-item-chains-chain"
colorless
key={chain}
network={currentNetwork}
size={28}
/>
);
})}
</div>
</Tooltip>
);
};
Loading