diff --git a/src/pages/Address/Cells.tsx b/src/pages/Address/Cells.tsx index f04e33ef3..01efe1492 100644 --- a/src/pages/Address/Cells.tsx +++ b/src/pages/Address/Cells.tsx @@ -2,7 +2,7 @@ import { type FC, useState, useRef, useEffect } from 'react' import { useTranslation } from 'react-i18next' import BigNumber from 'bignumber.js' import { useInfiniteQuery } from '@tanstack/react-query' -import { explorerService } from '../../services/ExplorerService' +import { explorerService, LiveCell } from '../../services/ExplorerService' import SUDTTokenIcon from '../../assets/sudt_token.png' import CKBTokenIcon from './ckb_token_icon.png' import { ReactComponent as CopyIcon } from './copy.svg' @@ -38,12 +38,145 @@ const initialPageParams = { size: 10, sort: 'capacity.desc' } const ATTRIBUTE_LENGTH = 18 -const Cells: FC<{ address: string; count: number }> = ({ address, count }) => { - const [params] = useState(initialPageParams) - const loadMoreRef = useRef(null) +const Cell: FC<{ cell: LiveCell }> = ({ cell }) => { const setToast = useSetToast() const { t } = useTranslation() + const handleCopy = (e: React.MouseEvent) => { + e.stopPropagation() + e.preventDefault() + const { detail } = e.currentTarget.dataset + if (!detail) return + navigator.clipboard.writeText(detail).then(() => { + setToast({ message: t('common.copied') }) + }) + } + + const ckb = new BigNumber(shannonToCkb(+cell.capacity)).toFormat() + const title = `${cell.txHash.slice(0, 8)}...${cell.txHash.slice(-8)}#${cell.cellIndex}` + const link = `/transaction/${cell.txHash}?${new URLSearchParams({ + page_of_outputs: Math.ceil(+cell.cellIndex / PAGE_SIZE).toString(), + })}` + const assetType: string = cell.extraInfo?.type ?? cell.cellType + let icon: string | React.ReactElement | null = null + let assetName = null + let attribute = null + let detailInfo = null + + switch (assetType) { + case 'ckb': { + if (cell.typeHash) { + icon = + assetName = 'UNKNOWN ASSET' + attribute = `TYPE HASH: ${cell.typeHash.slice(0, 10)}...` + detailInfo = cell.typeHash + break + } + if (cell.data !== '0x') { + // TODO: indicate this is a contentful cell + icon = + assetName = 'DATA' + if (cell.data.length > ATTRIBUTE_LENGTH) { + attribute = `${cell.data.slice(0, ATTRIBUTE_LENGTH)}...` + } else { + attribute = cell.data + } + detailInfo = cell.data + break + } + icon = CKBTokenIcon + assetName = 'CKB' + attribute = ckb + detailInfo = BigNumber(cell.capacity).toFormat({ groupSeparator: '' }) + break + } + case 'udt': + case 'omiga_inscription': { + icon = SUDTTokenIcon + assetName = cell.extraInfo.symbol || t('udt.inscription') + attribute = cell.extraInfo.decimal + ? parseUDTAmount(cell.extraInfo.amount, cell.extraInfo.decimal) + : 'Unknown UDT amount' + detailInfo = cell.extraInfo.amount + break + } + case 'spore_cell': { + icon = + assetName = 'DOB' + if (cell.data.length > ATTRIBUTE_LENGTH) { + attribute = `${cell.data.slice(0, ATTRIBUTE_LENGTH)}...` + } else { + attribute = cell.data + } + detailInfo = cell.data + break + } + case 'spore_cluster': { + icon = + assetName = 'Spore Cluster' + if (cell.data.length > ATTRIBUTE_LENGTH) { + attribute = `${cell.data.slice(0, ATTRIBUTE_LENGTH)}...` + } else { + attribute = cell.data + } + detailInfo = cell.data + break + } + case 'nrc_721': { + icon = SUDTTokenIcon + assetName = 'NRC 721' + attribute = '-' + break + } + case 'm_nft': { + icon = SUDTTokenIcon + assetName = cell.extraInfo.className + attribute = `#${parseInt(cell.extraInfo.tokenId, 16)}` + break + } + default: { + icon = SUDTTokenIcon + assetName = 'UNKNOWN' + attribute = '-' + } + } + const outPoint = { + tx_hash: cell.txHash, + index: `0x${cell.cellIndex.toString(16)}`, + } + + return ( +
  • +
    + {title} + + + {`${ckb} CKB`} +
    +
    + {typeof icon === 'string' ? {assetName : null} + {icon && typeof icon !== 'string' ? icon : null} +
    +
    {assetName}
    +
    + {attribute} + {detailInfo ? ( + + ) : null} +
    +
    +
    +
  • + ) +} + +const Cells: FC<{ address: string; count: number }> = ({ address, count }) => { + const [params] = useState(initialPageParams) + const loadMoreRef = useRef(null) const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery( ['address live cells', address, params.size, params.sort], @@ -80,16 +213,6 @@ const Cells: FC<{ address: string; count: number }> = ({ address, count }) => { } }, [isListDisplayed, fetchNextPage]) - const handleCopy = (e: React.MouseEvent) => { - e.stopPropagation() - e.preventDefault() - const { detail } = e.currentTarget.dataset - if (!detail) return - navigator.clipboard.writeText(detail).then(() => { - setToast({ message: t('common.copied') }) - }) - } - if (!isListDisplayed) { return null } @@ -100,133 +223,9 @@ const Cells: FC<{ address: string; count: number }> = ({ address, count }) => {
    UTXO: {count.toLocaleString('en')}
      - {cells.map(cell => { - const ckb = new BigNumber(shannonToCkb(+cell.capacity)).toFormat() - const title = `${cell.txHash.slice(0, 8)}...${cell.txHash.slice(-8)}#${cell.cellIndex}` - const link = `/transaction/${cell.txHash}?${new URLSearchParams({ - page_of_outputs: Math.ceil(+cell.cellIndex / PAGE_SIZE).toString(), - })}` - const assetType: string = cell.extraInfo?.type ?? cell.cellType - let icon: string | React.ReactElement | null = null - let assetName = null - let attribute = null - let detailInfo = null - - switch (assetType) { - case 'ckb': { - if (cell.typeHash) { - icon = - assetName = 'UNKNOWN ASSET' - attribute = `TYPE HASH: ${cell.typeHash.slice(0, 10)}...` - detailInfo = cell.typeHash - break - } - if (cell.data !== '0x') { - // TODO: indicate this is a contentful cell - icon = - assetName = 'DATA' - if (cell.data.length > ATTRIBUTE_LENGTH) { - attribute = `${cell.data.slice(0, ATTRIBUTE_LENGTH)}...` - } else { - attribute = cell.data - } - detailInfo = cell.data - break - } - icon = CKBTokenIcon - assetName = 'CKB' - attribute = ckb - detailInfo = BigNumber(cell.capacity).toFormat({ groupSeparator: '' }) - break - } - case 'udt': - case 'omiga_inscription': { - icon = SUDTTokenIcon - assetName = cell.extraInfo.symbol || t('udt.inscription') - attribute = cell.extraInfo.decimal - ? parseUDTAmount(cell.extraInfo.amount, cell.extraInfo.decimal) - : 'Unknown UDT amount' - detailInfo = cell.extraInfo.amount - break - } - case 'spore_cell': { - icon = - assetName = 'DOB' - if (cell.data.length > ATTRIBUTE_LENGTH) { - attribute = `${cell.data.slice(0, ATTRIBUTE_LENGTH)}...` - } else { - attribute = cell.data - } - detailInfo = cell.data - break - } - case 'spore_cluster': { - icon = - assetName = 'Spore Cluster' - if (cell.data.length > ATTRIBUTE_LENGTH) { - attribute = `${cell.data.slice(0, ATTRIBUTE_LENGTH)}...` - } else { - attribute = cell.data - } - detailInfo = cell.data - break - } - case 'nrc_721': { - icon = SUDTTokenIcon - assetName = 'NRC 721' - attribute = '-' - break - } - case 'm_nft': { - icon = SUDTTokenIcon - assetName = cell.extraInfo.className - attribute = `#${parseInt(cell.extraInfo.tokenId, 16)}` - break - } - default: { - icon = SUDTTokenIcon - assetName = 'UNKNOWN' - attribute = '-' - } - } - const outPoint = { - tx_hash: cell.txHash, - index: `0x${cell.cellIndex.toString(16)}`, - } - - return ( -
    • -
      - {title} - - - {`${ckb} CKB`} -
      -
      - {typeof icon === 'string' ? {assetName : null} - {icon && typeof icon !== 'string' ? icon : null} -
      -
      {assetName}
      -
      - {attribute} - {detailInfo ? ( - - ) : null} -
      -
      -
      -
    • - ) - })} + {cells.map(cell => ( + + ))}
    {isFetchingNextPage ? Loading... : null} {!hasNextPage || isFetchingNextPage ? null : ( diff --git a/src/services/ExplorerService/fetcher.ts b/src/services/ExplorerService/fetcher.ts index 4237da4a1..e1dd08860 100644 --- a/src/services/ExplorerService/fetcher.ts +++ b/src/services/ExplorerService/fetcher.ts @@ -5,7 +5,14 @@ import { ReactNode } from 'react' import { pick } from '../../utils/object' import { toCamelcase } from '../../utils/util' import { requesterV1, requesterV2 } from './requester' -import { ChartItem, NervosDaoDepositor, Response, SupportedExportTransactionType, TransactionRecord } from './types' +import { + ChartItem, + NervosDaoDepositor, + Response, + SupportedExportTransactionType, + TransactionRecord, + LiveCell, +} from './types' import { assert } from '../../utils/error' import { Cell } from '../../models/Cell' import { Script } from '../../models/Script' @@ -75,31 +82,7 @@ export const apiFetcher = { // sort field, block_timestamp, capacity // sort type, asc, desc fetchAddressLiveCells: (address: string, page: number, size: number, sort?: string) => { - return v1GetUnwrappedPagedList<{ - cellType: 'spore_cell' - txHash: string - cellIndex: number - typeHash: string - data: string - capacity: string - occupiedCapacity: string - blockTimestamp: string - blockNumber: string - typeScript: Script - lockScript: Script - extraInfo: { - symbol: string - amount: string - decimal: string - typeHash: string - published: boolean - displayName: string - uan: string - type: 'ckb' | 'udt' | 'nrc_721' | 'm_nft' - className: string - tokenId: string - } - }>(`address_live_cells/${address}`, { + return v1GetUnwrappedPagedList(`address_live_cells/${address}`, { params: { page, page_size: size, diff --git a/src/services/ExplorerService/types.ts b/src/services/ExplorerService/types.ts index 17e676966..9789d13a1 100644 --- a/src/services/ExplorerService/types.ts +++ b/src/services/ExplorerService/types.ts @@ -287,3 +287,35 @@ interface FetchStatusValue { export type FetchStatus = keyof FetchStatusValue export type SupportedExportTransactionType = 'address_transactions' | 'blocks' | 'udts' | 'nft' | 'omiga_inscriptions' + +export interface Script { + codeHash: string + args: string + hashType: string +} + +export interface LiveCell { + cellType: string + txHash: string + cellIndex: number + typeHash: string + data: string + capacity: string + occupiedCapacity: string + blockTimestamp: string + blockNumber: string + typeScript: Script + lockScript: Script + extraInfo: { + symbol: string + amount: string + decimal: string + typeHash: string + published: boolean + displayName: string + uan: string + type: 'ckb' | 'udt' | 'nrc_721' | 'm_nft' + className: string + tokenId: string + } +}