Files
tom goriunovandGitHub ebbee2ba57 Multichain explorer v2 (#3259)
* Support redirect to a correct view from search

* show domains on address page

* migrate to new API version to display address domains

* another api update

* new design for chain widget on home page + tests

* chain widget styles for mobile devices

* ecosystems page

* update types

* add ecosystems to navigation

* address portfolio: pt.1

* portfolio tokens list

* filter portfolio by chain

* skip redirect when navigating to search result by clicking on "show all results" link

* net worth block design

* add filter input to chain select and fix tokens table

* calculate address portfolio

* adjust card logic

* support custom token types in multichain explorer

* handle case when new worth is 0

* add text filter to tokens list

* fixes

* demo envs

* portfolio tests

* ecosystems test

* change signature for getTokenTypes function

* fix adding token to the wallet from the list

* lazy load all address domains

* show contract creator and implementations info

* fix tests

* add poor reputation tokens toggle

* tweaks

* dim celo native tokens

* disable env validator

* fix tests

* remove tokens with 0 usd value from top tokens

* fixes

* preserve selected chain when switching tabs on address page

* add hints to stats widgets on home page

* update envs for demo

* fix portfolio cards and home page

* update styles for contract creator and implementations

* fix select issues

* add chains from cluster to network menu

* hide csv export link on empty logs page

* update screenshots

* rollback select fix

* update cut length calculation for portfolio cards

* fix chain select

* update screenshots

* fix tests

* more fixes

* add sorting to ecosystems table

* fix test

* fixes

* fix loading state in ecosystems page

* review fixes

* fix tests
2026-02-25 10:45:09 +01:00

146 lines
4.1 KiB
TypeScript

import BigNumber from 'bignumber.js';
import type { AddressTokenBalance } from 'types/api/address';
import config from 'configs/app';
import sumBnReducer from 'lib/bigint/sumBnReducer';
import { isFungibleTokenType } from 'lib/token/tokenTypes';
import { ZERO } from 'toolkit/utils/consts';
const isNativeToken = (token: TokenEnhancedData) =>
config.UI.views.address.nativeTokenAddress &&
token.token.address_hash.toLowerCase() === config.UI.views.address.nativeTokenAddress.toLowerCase();
export type TokenEnhancedData = AddressTokenBalance & {
usd?: BigNumber ;
chain_values?: Record<string, string>;
};
export type Sort = 'desc' | 'asc';
export type TokenSelectData = Record<string, TokenSelectDataItem>;
export interface TokenSelectDataItem {
items: Array<TokenEnhancedData>;
isOverflow: boolean;
}
type TokenGroup = [string, TokenSelectDataItem];
const NFT_TOKEN_GROUPS_ORDER = [ 'ERC-721', 'ERC-1155', 'ERC-404' ] as const;
export const sortTokenGroups = (groupA: TokenGroup, groupB: TokenGroup) => {
const additionalTypeIds = config.chain.additionalTokenTypes.map((item) => item.id);
const tokenGroupsOrder = [
'ERC-20',
...additionalTypeIds,
...NFT_TOKEN_GROUPS_ORDER,
] as const;
const orderA = tokenGroupsOrder.indexOf(groupA[0] as (typeof tokenGroupsOrder)[number]);
const orderB = tokenGroupsOrder.indexOf(groupB[0] as (typeof tokenGroupsOrder)[number]);
const normalizedA = orderA === -1 ? 999 : orderA;
const normalizedB = orderB === -1 ? 999 : orderB;
if (normalizedA !== normalizedB) {
return normalizedA > normalizedB ? 1 : -1;
}
return groupA[0].localeCompare(groupB[0]);
};
const sortErc1155or404Tokens = (sort: Sort) => (dataA: AddressTokenBalance, dataB: AddressTokenBalance) => {
if (dataA.value === dataB.value) {
return 0;
}
if (sort === 'desc') {
return Number(dataA.value) > Number(dataB.value) ? -1 : 1;
}
return Number(dataA.value) > Number(dataB.value) ? 1 : -1;
};
const sortErc20Tokens = (sort: Sort) => (dataA: TokenEnhancedData, dataB: TokenEnhancedData) => {
if (!dataA.usd && !dataB.usd) {
return 0;
}
// keep tokens without usd value in the end of the group
if (!dataB.usd) {
return -1;
}
if (!dataA.usd) {
return 0;
}
if (sort === 'desc') {
return dataA.usd.gt(dataB.usd) ? -1 : 1;
}
return dataA.usd.gt(dataB.usd) ? 1 : -1;
};
const sortErc721Tokens = () => () => 0;
export const sortingFns = {
'ERC-20': sortErc20Tokens,
'ERC-721': sortErc721Tokens,
'ERC-1155': sortErc1155or404Tokens,
'ERC-404': sortErc1155or404Tokens,
};
export const getSortingFn = (typeId: string) => {
return sortingFns[typeId as keyof typeof sortingFns] || sortErc20Tokens;
};
export const filterTokens = (searchTerm: string) => ({ token }: AddressTokenBalance) => {
if (!token.name) {
return !searchTerm ? true : token.address_hash.toLowerCase().includes(searchTerm);
}
return token.name?.toLowerCase().includes(searchTerm);
};
export const calculateUsdValue = (data: AddressTokenBalance): TokenEnhancedData => {
const isFungibleToken = isFungibleTokenType(data.token.type);
if (!isFungibleToken) {
return data;
}
const exchangeRate = data.token.exchange_rate;
if (!exchangeRate) {
return data;
}
const decimals = Number(data.token.decimals || '18');
return {
...data,
usd: BigNumber(data.value).div(BigNumber(10 ** decimals)).multipliedBy(BigNumber(exchangeRate)),
};
};
export interface TokensTotalInfo {
usd: BigNumber;
num: number;
isOverflow: boolean;
}
export const getTokensTotalInfo = (data: TokenSelectData): TokensTotalInfo => {
const usd = Object.values(data)
.map(({ items }) => items.filter((item) => !isNativeToken(item)).reduce(usdValueReducer, ZERO))
.reduce(sumBnReducer, ZERO);
const num = Object.values(data)
.map(({ items }) => items.length)
.reduce((result, item) => result + item, 0);
const isOverflow = Object.values(data).some(({ isOverflow }) => isOverflow);
return { usd, num, isOverflow };
};
const usdValueReducer = (result: BigNumber, item: TokenEnhancedData) => !item.usd ? result : result.plus(BigNumber(item.usd));