Skip to content

Commit

Permalink
[explorer] Add balance formatting for owned objects (MystenLabs#5818)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jordan-Mysten authored Nov 3, 2022
1 parent 106c54d commit 0a1e1f5
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 53 deletions.
6 changes: 3 additions & 3 deletions apps/explorer/cypress/e2e/static/e2e.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,15 +155,15 @@ describe('End-to-end Tests', () => {

cy.visit(`/addresses/${address}`);

cy.get(`${rowCSSSelector(1)} ${label}`).contains('0x2::USD::USD');
cy.get(`${rowCSSSelector(1)} ${label}`).contains('USD');
cy.get(`${rowCSSSelector(1)} ${count}`).contains('2');
cy.get(`${rowCSSSelector(1)} ${balance}`).contains(
'9007199254740993'
'9,007,199,254,740,993'
);

cy.get(`${rowCSSSelector(2)} ${label}`).contains('SUI');
cy.get(`${rowCSSSelector(2)} ${count}`).contains('2');
cy.get(`${rowCSSSelector(2)} ${balance}`).contains('200');
cy.get(`${rowCSSSelector(2)} ${balance}`).contains('0.0000002');
});
});

Expand Down
98 changes: 62 additions & 36 deletions apps/explorer/src/components/ownedobjects/views/OwnedCoinView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,52 @@
// SPDX-License-Identifier: Apache-2.0

import { Coin } from '@mysten/sui.js';
import React, { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';

import { ReactComponent as OpenIcon } from '../../../assets/SVGIcons/12px/ShowNHideDown.svg';
import { ReactComponent as ClosedIcon } from '../../../assets/SVGIcons/12px/ShowNHideRight.svg';
import { handleCoinType } from '../../../utils/stringUtils';
import Longtext from '../../longtext/Longtext';
import Pagination from '../../pagination/Pagination';
import { type DataType, ITEMS_PER_PAGE } from '../OwnedObjectConstants';

import styles from '../styles/OwnedCoin.module.css';

import { useFormatCoin } from '~/hooks/useFormatCoin';

function CoinItem({
id,
balance,
coinType,
}: {
id: string;
balance?: bigint | null;
coinType?: string | null;
}) {
const [formattedBalance] = useFormatCoin(balance, coinType);

return (
<div className={styles.singlecoin}>
<div className={styles.openrow}>
<div className={styles.label}>Object ID</div>
<div className={`${styles.oneline} ${styles.value}`}>
<Longtext text={id} category="objects" />
</div>
</div>
<div className={styles.openrow}>
<div className={styles.label}>Balance</div>
<div className={styles.value}>{formattedBalance}</div>
</div>
</div>
);
}

function SingleCoinView({
results,
coinLabel,
coinType,
currentPage,
}: {
results: DataType;
coinLabel: string;
coinType: string;
currentPage: number;
}) {
const [isOpen, setIsOpen] = useState(false);
Expand All @@ -34,7 +62,23 @@ function SingleCoinView({
setIsOpen(false);
}, [currentPage]);

const subObjList = results.filter(({ Type }) => Type === coinLabel);
const subObjList = results.filter(({ Type }) => Type === coinType);

const totalBalance =
subObjList[0]._isCoin &&
subObjList.every((el) => el.balance !== undefined)
? subObjList.reduce(
(prev, current) => prev + current.balance!,
Coin.getZero()
)
: null;

const extractedCoinType = Coin.getCoinType(coinType);

const [formattedTotalBalance, symbol] = useFormatCoin(
totalBalance,
extractedCoinType
);

return (
<div
Expand All @@ -48,7 +92,7 @@ function SingleCoinView({
className={styles.oneline}
data-testid="ownedcoinlabel"
>
{handleCoinType(coinLabel)}
{symbol}
</div>
</div>
<div
Expand All @@ -58,37 +102,19 @@ function SingleCoinView({
{subObjList.length}
</div>
<div className={styles.balance} data-testid="ownedcoinbalance">
{subObjList[0]._isCoin &&
subObjList.every((el) => el.balance !== undefined)
? `${subObjList.reduce(
(prev, current) => prev + current.balance!,
Coin.getZero()
)}`
: ''}
{formattedTotalBalance}
</div>
</div>

{isOpen && (
<div className={styles.openbody}>
{subObjList.map((subObj, index) => (
<div key={index} className={styles.singlecoin}>
<div className={styles.openrow}>
<div className={styles.label}>Object ID</div>
<div
className={`${styles.oneline} ${styles.value}`}
>
<Longtext
text={subObj.id}
category="objects"
/>
</div>
</div>
<div className={styles.openrow}>
<div className={styles.label}>Balance</div>
<div className={styles.value}>
{subObj.balance?.toString()}
</div>
</div>
</div>
{subObjList.map((subObj) => (
<CoinItem
key={subObj.id}
id={subObj.id}
coinType={extractedCoinType}
balance={subObj.balance}
/>
))}
</div>
)}
Expand All @@ -115,11 +141,11 @@ export default function OwnedCoinView({ results }: { results: DataType }) {
(currentPage - 1) * ITEMS_PER_PAGE,
currentPage * ITEMS_PER_PAGE
)
.map((typeV) => (
.map((type) => (
<SingleCoinView
key={typeV}
key={type}
results={results}
coinLabel={typeV}
coinType={type}
currentPage={currentPage}
/>
))}
Expand Down
11 changes: 4 additions & 7 deletions apps/explorer/src/pages/transaction-result/SendReceiveView.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { COIN_TYPE_ARG_REGEX } from '@mysten/sui.js';
import { Coin } from '@mysten/sui.js';
import { useQuery } from '@tanstack/react-query';
import cl from 'clsx';

Expand All @@ -23,9 +23,6 @@ type TxAddress = {
objects?: string[];
};

const getCoinLabelFromObjType = (objType: string) =>
COIN_TYPE_ARG_REGEX.exec(objType)?.[1];

function MultipleRecipients({ sender, recipient, amount, objects }: TxAddress) {
const rpc = useRpc();

Expand Down Expand Up @@ -116,10 +113,10 @@ function MultipleRecipients({ sender, recipient, amount, objects }: TxAddress) {
}

function Amount({ amount, label }: { amount: bigint; label: string }) {
const coinBoilerPlateRemoved = getCoinLabelFromObjType(label);
const coinType = Coin.getCoinType(label);
const [formattedAmount, suffix] = useFormatCoin(
amount,
coinBoilerPlateRemoved,
coinType,
CoinFormat.FULL
);
return (
Expand All @@ -144,7 +141,7 @@ function SingleAmount({
async () => {
const obj = await rpc.getObject(objectId);

return getCoinLabelFromObjType(parseObjectType(obj));
return Coin.getCoinType(parseObjectType(obj));
}
);

Expand Down
5 changes: 0 additions & 5 deletions apps/explorer/src/utils/stringUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@ export function hexToAscii(hex: string) {
export const trimStdLibPrefix = (str: string): string =>
str.replace(/^0x2::/, '');

export const handleCoinType = (str: string): string =>
str === '0x2::coin::Coin<0x2::sui::SUI>'
? 'SUI'
: str.match(/^([a-zA-Z0-9:]*)<([a-zA-Z0-9:]*)>$/)?.[2] || str;

export const findIPFSvalue = (url: string): string | undefined =>
url.match(/^ipfs:\/\/(.*)/)?.[1];

Expand Down
9 changes: 7 additions & 2 deletions sdk/typescript/src/types/framework.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,14 @@ export class Coin {
return Coin.getType(data)?.startsWith(COIN_TYPE) ?? false;
}

static getCoinType(type: string) {
const [, res] = type.match(COIN_TYPE_ARG_REGEX) ?? [];
return res || null;
}

static getCoinTypeArg(obj: ObjectData) {
const res = Coin.getType(obj)?.match(COIN_TYPE_ARG_REGEX);
return res ? res[1] : null;
const type = Coin.getType(obj);
return type ? Coin.getCoinType(type) : null;
}

static isSUI(obj: ObjectData) {
Expand Down

0 comments on commit 0a1e1f5

Please sign in to comment.