From 554afdfc8c441e2bc7caa8cb63614d0034900563 Mon Sep 17 00:00:00 2001 From: Jibz Date: Mon, 2 May 2022 13:05:49 -0400 Subject: [PATCH 1/6] added tx status and txGas on recent transaction --- .../transaction-card/RecentTxCard.module.css | 14 ++++-- .../transaction-card/RecentTxCard.tsx | 48 ++++++++++++++++--- .../transaction-result/TransactionResult.tsx | 4 +- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/explorer/client/src/components/transaction-card/RecentTxCard.module.css b/explorer/client/src/components/transaction-card/RecentTxCard.module.css index 9978b3d22f628..2eef933266ca2 100644 --- a/explorer/client/src/components/transaction-card/RecentTxCard.module.css +++ b/explorer/client/src/components/transaction-card/RecentTxCard.module.css @@ -15,7 +15,7 @@ div.txcardgridlarge { } div.txcardgrid > div { - @apply block pr-5; + @apply block; } div.txcardgrid > div:first-child { @@ -53,9 +53,17 @@ div.txadd { } .txsearch input { - @apply h-16 shadow-sm mr-0 ml-0 rounded-none pl-2 pr-2 border-solid border border-gray-100 text-base; + @apply shadow-sm mr-0 ml-0 rounded-none pl-2 pr-2 border-solid border border-gray-100 text-base; } .txsearch [type='submit'] { - @apply md:w-1/12 w-20 bg-offblack text-offwhite hover:text-offblack hover:bg-sui; + @apply md:w-1/12 w-20 bg-offblack text-offwhite hover:text-offblack hover:bg-sui h-16 shadow-sm border-solid border border-gray-100; +} + +.failure { + @apply text-red-300; +} + +.success { + @apply text-sui; } diff --git a/explorer/client/src/components/transaction-card/RecentTxCard.tsx b/explorer/client/src/components/transaction-card/RecentTxCard.tsx index 9367b9da16fdf..9e026a7a362a8 100644 --- a/explorer/client/src/components/transaction-card/RecentTxCard.tsx +++ b/explorer/client/src/components/transaction-card/RecentTxCard.tsx @@ -16,7 +16,12 @@ import theme from '../../styles/theme.module.css'; import { DefaultRpcClient as rpc } from '../../utils/api/DefaultRpcClient'; import ErrorResult from '../error-result/ErrorResult'; -import type { CertifiedTransaction, GetTxnDigestsResponse } from 'sui.js'; +import type { + CertifiedTransaction, + GetTxnDigestsResponse, + TransactionEffectsResponse, + ExecutionStatus, +} from 'sui.js'; import styles from './RecentTxCard.module.css'; @@ -25,19 +30,35 @@ const initState = { latestTx: [], }; +const getGasFeesAndStatus = (txStatusData: ExecutionStatus) => { + const istxSucces = Object.keys(txStatusData)[0].toLowerCase(); + const txGasObj = Object.values(txStatusData)[0]; + const txGas = + txGasObj.gas_cost.computation_cost + + txGasObj.gas_cost.storage_cost - + txGasObj.gas_cost.storage_rebate; + return { + istxSucces, + txGas, + }; +}; + const getRecentTransactions = async (txNum: number) => { try { // Get the latest transactions - // TODO add batch transaction kind + // TODO add batch transaction kind TransactionDigest // TODO sui.js to get the latest transactions meta data const transactions = await rpc .getRecentTransactions(txNum) .then((res: GetTxnDigestsResponse) => res); + const txLatest = await Promise.all( transactions.map(async (tx) => { return await rpc - .getTransaction(tx[1]) - .then((res: CertifiedTransaction) => { + .getTransactionWithEffects(tx[1]) + .then((txEff: TransactionEffectsResponse) => { + const res: CertifiedTransaction = txEff.certificate; + const singleTransaction = getSingleTransactionKind( res.data ); @@ -51,10 +72,17 @@ const getRecentTransactions = async (txNum: number) => { res.data )?.recipient; + // TODO use ExecutionStatus and add a method on sui.js to get the gas data + const txStatusData = getGasFeesAndStatus( + txEff.effects.status + ); + // Calculate the gas used + return { block: tx[0], txId: tx[1], - // success: txData ? true : false, + success: txStatusData.istxSucces, + txGas: txStatusData.txGas, kind: txKind, From: res.data.sender, ...(recipient @@ -158,7 +186,8 @@ function LatestTxCard() { )} >
TxId
-
Tx Type
+
TxType
+
Status
Gas
Sender & Receiver
@@ -178,7 +207,12 @@ function LatestTxCard() {
{tx.kind}
-
10
+
+ {tx.success === 'success' ? '✔' : '✖'} +
+
{tx.txGas}
From: diff --git a/explorer/client/src/pages/transaction-result/TransactionResult.tsx b/explorer/client/src/pages/transaction-result/TransactionResult.tsx index 9f9d0d4a60e6e..e3ebdc585014d 100644 --- a/explorer/client/src/pages/transaction-result/TransactionResult.tsx +++ b/explorer/client/src/pages/transaction-result/TransactionResult.tsx @@ -64,7 +64,9 @@ function fetchTransactionData(txId: string | undefined) { }); } - return rpc.getTransaction(txId).then((objState) => objState); + return rpc + .getTransaction(txId) + .then((objState: CertifiedTransaction) => objState); } catch (error) { throw error; } From bd44c377939494e311ad25168a0d4ef62550de18 Mon Sep 17 00:00:00 2001 From: Jibz Date: Mon, 2 May 2022 18:02:28 -0400 Subject: [PATCH 2/6] txDatail use getTransactionWithEffects with txmeta success status --- .../transaction-card/RecentTxCard.tsx | 1 - .../TransactionCard.module.css | 10 ++- .../transaction-card/TransactionCard.tsx | 61 ++++++++++++++++--- .../transaction-result/TransactionResult.tsx | 61 ++++++++++++++++--- 4 files changed, 113 insertions(+), 20 deletions(-) diff --git a/explorer/client/src/components/transaction-card/RecentTxCard.tsx b/explorer/client/src/components/transaction-card/RecentTxCard.tsx index 9e026a7a362a8..939a7a9aa83e6 100644 --- a/explorer/client/src/components/transaction-card/RecentTxCard.tsx +++ b/explorer/client/src/components/transaction-card/RecentTxCard.tsx @@ -58,7 +58,6 @@ const getRecentTransactions = async (txNum: number) => { .getTransactionWithEffects(tx[1]) .then((txEff: TransactionEffectsResponse) => { const res: CertifiedTransaction = txEff.certificate; - const singleTransaction = getSingleTransactionKind( res.data ); diff --git a/explorer/client/src/components/transaction-card/TransactionCard.module.css b/explorer/client/src/components/transaction-card/TransactionCard.module.css index 06250bab11a58..02accb46a0785 100644 --- a/explorer/client/src/components/transaction-card/TransactionCard.module.css +++ b/explorer/client/src/components/transaction-card/TransactionCard.module.css @@ -37,7 +37,7 @@ div.status-success { } div.status-fail { - @apply bg-red-300; + @apply text-red-300; } div.status-pending { @@ -53,7 +53,7 @@ div.action-mutate { } div.grouped { - @apply border-transparent; + @apply border-transparent !important; } ul.listitems { @@ -69,5 +69,9 @@ ul.listitems { } .listitems .sublist { - @apply mt-2 list-none p-0; + @apply mt-2 list-none p-0 md:flex grid gap-2; +} + +.sublist .sublistlabel { + @apply w-[100px] font-bold; } diff --git a/explorer/client/src/components/transaction-card/TransactionCard.tsx b/explorer/client/src/components/transaction-card/TransactionCard.tsx index 722a8e56cacc5..286af57b5ce94 100644 --- a/explorer/client/src/components/transaction-card/TransactionCard.tsx +++ b/explorer/client/src/components/transaction-card/TransactionCard.tsx @@ -20,11 +20,18 @@ import type { import styles from './TransactionCard.module.css'; +type TxDataProps = CertifiedTransaction & { + txSuccess: boolean; + gasFee: number; + txError: string; +}; + // Generate an Arr of Obj with Label and Value // TODO rewrite to use sue.js, verify tx types and dynamically generate list -function formatTxResponse(tx: CertifiedTransaction, txId: string) { +function formatTxResponse(tx: TxDataProps, txId: string) { // Todo add batch kind const txKindName = getTransactionKind(tx.data); + return [ { label: 'Transaction ID', @@ -34,8 +41,8 @@ function formatTxResponse(tx: CertifiedTransaction, txId: string) { { // May change later label: 'Status', - value: 'Success', - classAttr: 'status-success', + value: tx.txSuccess ? 'Success' : `Failed - ${tx.txError}`, + classAttr: tx.txSuccess ? 'status-success' : 'status-fail', }, { label: 'Transaction Type', @@ -52,7 +59,6 @@ function formatTxResponse(tx: CertifiedTransaction, txId: string) { label: 'Gas Payment', value: tx.data.gas_payment[0], link: true, - className: 'grouped', }, { label: 'Gas Budget', @@ -63,6 +69,8 @@ function formatTxResponse(tx: CertifiedTransaction, txId: string) { value: tx.auth_sign_info.signatures, list: true, sublist: true, + // Todo - assumes only two itmes in list ['A', 'B'] + subLabel: ['Name', 'Signature'], }, ]; } @@ -144,7 +152,13 @@ function formatByTransactionKind( } type Props = { - txdata: CertifiedTransaction & { loadState: string; txId: string }; + txdata: CertifiedTransaction & { + loadState: string; + txId: string; + txSuccess: boolean; + gasFee: number; + txError: string; + }; }; function TransactionCard({ txdata }: Props) { @@ -197,9 +211,40 @@ function TransactionCard({ txdata }: Props) { l } > - { - sublist - } +
+ {itm.subLabel ? ( +
+ {' '} + { + itm + .subLabel[ + l + ] + } + + : +
+ ) : ( + '' + )} +
+ { + sublist + } +
+
) )} diff --git a/explorer/client/src/pages/transaction-result/TransactionResult.tsx b/explorer/client/src/pages/transaction-result/TransactionResult.tsx index e3ebdc585014d..e3a58fba6be0a 100644 --- a/explorer/client/src/pages/transaction-result/TransactionResult.tsx +++ b/explorer/client/src/pages/transaction-result/TransactionResult.tsx @@ -12,11 +12,21 @@ import theme from '../../styles/theme.module.css'; import { DefaultRpcClient as rpc } from '../../utils/api/DefaultRpcClient'; import { findDataFromID } from '../../utils/static/searchUtil'; -import type { CertifiedTransaction } from 'sui.js'; +import type { + CertifiedTransaction, + TransactionEffectsResponse, + ExecutionStatus, +} from 'sui.js'; import styles from './TransactionResult.module.css'; -type TxnState = CertifiedTransaction & { loadState: string; txId: string }; +type TxnState = CertifiedTransaction & { + loadState: string; + txId: string; + txSuccess: boolean; + gasFee: number; + txError: string; +}; // Todo update state to include Call types const initState: TxnState = { loadState: 'pending', @@ -39,6 +49,24 @@ const initState: TxnState = { epoch: 0, signatures: [], }, + txSuccess: false, + gasFee: 0, + txError: '', +}; + +const getGasFeesAndStatus = (txStatusData: ExecutionStatus) => { + const istxSucces = Object.keys(txStatusData)[0].toLowerCase(); + const txGasObj = Object.values(txStatusData)[0]; + const txGas = + txGasObj.gas_cost.computation_cost + + txGasObj.gas_cost.storage_cost - + txGasObj.gas_cost.storage_rebate; + + return { + istxSucces, + txGas, + // txErr: txStatusData.Failure || '', + }; }; const isStatic = process.env.REACT_APP_DATA !== 'static'; @@ -55,18 +83,24 @@ function fetchTransactionData(txId: string | undefined) { // resolve after one second return new Promise((resolve, reject) => { setTimeout(() => { - const staticObj = findDataFromID(txId, undefined); + const staticObj: CertifiedTransaction = findDataFromID( + txId, + undefined + ); if (!staticObj) { reject('txid not found'); } - resolve(staticObj); + resolve({ + certificate: staticObj, + effects: {}, + }); }, 1000); }); } return rpc - .getTransaction(txId) - .then((objState: CertifiedTransaction) => objState); + .getTransactionWithEffects(txId) + .then((txEff: TransactionEffectsResponse) => txEff); } catch (error) { throw error; } @@ -78,9 +112,20 @@ function TransactionResult() { useEffect(() => { fetchTransactionData(id) - .then((resp: any) => { + .then((txObj: any) => { + const txMeta = getGasFeesAndStatus(txObj.effects.status); setTxState({ - ...resp, + ...txObj.certificate, + txSuccess: txMeta.istxSucces === 'success', + gasFee: txMeta.txGas, + txError: + txMeta.istxSucces !== 'success' + ? txObj.effects.status.Failure.error[ + Object.keys( + txObj.effects.status.Failure.error + )[0] + ].error + : '', txId: id, loadState: 'loaded', }); From 8dcbeee1a474226ed92b6b6fed72f62f00458d67 Mon Sep 17 00:00:00 2001 From: Jibz Date: Mon, 2 May 2022 23:33:41 -0400 Subject: [PATCH 3/6] prevent empty search add tx update --- .../client/src/components/search/Search.tsx | 2 ++ .../transaction-card/RecentTxCard.module.css | 4 ++-- .../transaction-card/RecentTxCard.tsx | 8 +++++--- .../TransactionCard.module.css | 12 ++++++------ .../transaction-card/TransactionCard.tsx | 16 +++++++++++----- .../transaction-result/TransactionResult.tsx | 18 +++++++++--------- 6 files changed, 35 insertions(+), 25 deletions(-) diff --git a/explorer/client/src/components/search/Search.tsx b/explorer/client/src/components/search/Search.tsx index 11fba00cfb32d..2f6f8109bd165 100644 --- a/explorer/client/src/components/search/Search.tsx +++ b/explorer/client/src/components/search/Search.tsx @@ -17,6 +17,8 @@ function Search() { const handleSubmit = useCallback( (e: React.FormEvent) => { e.preventDefault(); + // Prevent empty search + if (!input.length) return; setPleaseWaitMode(true); navigateWithUnknown(input, navigate).then(() => { setInput(''); diff --git a/explorer/client/src/components/transaction-card/RecentTxCard.module.css b/explorer/client/src/components/transaction-card/RecentTxCard.module.css index 2eef933266ca2..ff00b02766367 100644 --- a/explorer/client/src/components/transaction-card/RecentTxCard.module.css +++ b/explorer/client/src/components/transaction-card/RecentTxCard.module.css @@ -33,7 +33,7 @@ div.txadd { } .txheader { - @apply bg-offblack text-offwhite rounded-none md:h-10 shadow-none !important; + @apply bg-offblack text-offwhite rounded-none md:h-10 shadow-none md:flex hidden !important; } .txtype { @@ -65,5 +65,5 @@ div.txadd { } .success { - @apply text-sui; + @apply text-green-400; } diff --git a/explorer/client/src/components/transaction-card/RecentTxCard.tsx b/explorer/client/src/components/transaction-card/RecentTxCard.tsx index 939a7a9aa83e6..e746c40e591cd 100644 --- a/explorer/client/src/components/transaction-card/RecentTxCard.tsx +++ b/explorer/client/src/components/transaction-card/RecentTxCard.tsx @@ -8,6 +8,7 @@ import { getSingleTransactionKind, getTransactionKind, getTransferTransaction, + getExecutionStatusType } from 'sui.js'; import Longtext from '../../components/longtext/Longtext'; @@ -21,6 +22,7 @@ import type { GetTxnDigestsResponse, TransactionEffectsResponse, ExecutionStatus, + ExecutionStatusType, } from 'sui.js'; import styles from './RecentTxCard.module.css'; @@ -31,14 +33,14 @@ const initState = { }; const getGasFeesAndStatus = (txStatusData: ExecutionStatus) => { - const istxSucces = Object.keys(txStatusData)[0].toLowerCase(); + const txStatus: ExecutionStatusType = getExecutionStatusType(txStatusData); const txGasObj = Object.values(txStatusData)[0]; const txGas = txGasObj.gas_cost.computation_cost + txGasObj.gas_cost.storage_cost - txGasObj.gas_cost.storage_rebate; return { - istxSucces, + txStatus: txStatus.toLocaleLowerCase(), txGas, }; }; @@ -80,7 +82,7 @@ const getRecentTransactions = async (txNum: number) => { return { block: tx[0], txId: tx[1], - success: txStatusData.istxSucces, + success: txStatusData.txStatus, txGas: txStatusData.txGas, kind: txKind, From: res.data.sender, diff --git a/explorer/client/src/components/transaction-card/TransactionCard.module.css b/explorer/client/src/components/transaction-card/TransactionCard.module.css index 02accb46a0785..998ae743d3639 100644 --- a/explorer/client/src/components/transaction-card/TransactionCard.module.css +++ b/explorer/client/src/components/transaction-card/TransactionCard.module.css @@ -9,7 +9,7 @@ div.columnheader > div { } div.txcardgrid { - @apply grid md:grid-cols-3 sm:grid-cols-1 gap-2 border-0 pt-3 pb-3 pl-8 pr-8 items-center; + @apply grid md:grid-cols-3 sm:grid-cols-1 gap-2 border-0 pt-3 pb-3 md:pl-8 md:pr-8 pl-4 pr-4 items-center; } div.txcardgrid:nth-child(2n + 1) { @@ -17,7 +17,7 @@ div.txcardgrid:nth-child(2n + 1) { } div.txcardgrid:first-child { - @apply bg-offblack rounded-none items-center pt-5 pb-5 font-black shadow-md border-2 border-coolGray-400 h-10; + @apply bg-offblack rounded-none items-center pt-5 pb-5 font-black shadow-md border-2 border-coolGray-400 ; } div.txcardgrid > div:first-child { @@ -33,7 +33,7 @@ div.txcardgridlarge { } div.status-success { - @apply text-sui; + @apply text-green-600; } div.status-fail { @@ -69,9 +69,9 @@ ul.listitems { } .listitems .sublist { - @apply mt-2 list-none p-0 md:flex grid gap-2; + @apply mt-2 list-none p-0 md:flex grid gap-2 pr-0; } -.sublist .sublistlabel { - @apply w-[100px] font-bold; +div.sublist .sublistlabel{ + @apply w-28 flex-none; } diff --git a/explorer/client/src/components/transaction-card/TransactionCard.tsx b/explorer/client/src/components/transaction-card/TransactionCard.tsx index 286af57b5ce94..7015143816dab 100644 --- a/explorer/client/src/components/transaction-card/TransactionCard.tsx +++ b/explorer/client/src/components/transaction-card/TransactionCard.tsx @@ -60,6 +60,10 @@ function formatTxResponse(tx: TxDataProps, txId: string) { value: tx.data.gas_payment[0], link: true, }, + { + label: 'Gas Fee', + value: tx.gasFee, + }, { label: 'Gas Budget', value: tx.data.gas_budget, @@ -192,18 +196,19 @@ function TransactionCard({ txdata }: Props) { {itm.value.map( (list: any, n: number) => itm.sublist ? ( -
    +
    {list.map( ( sublist: string, l: number ) => ( -
  • - {' '} + { itm .subLabel[ @@ -245,10 +250,11 @@ function TransactionCard({ txdata }: Props) { }
- +
) )} - + + ) : (
  • { - const istxSucces = Object.keys(txStatusData)[0].toLowerCase(); + const txStatus: ExecutionStatusType = getExecutionStatusType(txStatusData); const txGasObj = Object.values(txStatusData)[0]; - const txGas = + const txGasFees = txGasObj.gas_cost.computation_cost + txGasObj.gas_cost.storage_cost - txGasObj.gas_cost.storage_rebate; return { - istxSucces, - txGas, - // txErr: txStatusData.Failure || '', + txStatus: txStatus.toLowerCase(), + txGas: txGasFees, }; }; @@ -116,10 +116,10 @@ function TransactionResult() { const txMeta = getGasFeesAndStatus(txObj.effects.status); setTxState({ ...txObj.certificate, - txSuccess: txMeta.istxSucces === 'success', + txSuccess: txMeta.txStatus === 'success', gasFee: txMeta.txGas, txError: - txMeta.istxSucces !== 'success' + txMeta.txStatus !== 'success' ? txObj.effects.status.Failure.error[ Object.keys( txObj.effects.status.Failure.error @@ -131,7 +131,7 @@ function TransactionResult() { }); }) .catch((err) => { - // remove this section in production + console.log(err) setTxState({ ...initState, loadState: 'fail', From 897b7fd163fe9b0c6206b31455c87624f6184e3b Mon Sep 17 00:00:00 2001 From: Jibz Date: Mon, 2 May 2022 23:35:40 -0400 Subject: [PATCH 4/6] added getExecutionStatusType and ExecutionStatusType --- sdk/typescript/src/index.guard.ts | 9 ++++++++- sdk/typescript/src/types/transactions.ts | 7 +++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/sdk/typescript/src/index.guard.ts b/sdk/typescript/src/index.guard.ts index 75c6f62776278..646a72a3b8fc8 100644 --- a/sdk/typescript/src/index.guard.ts +++ b/sdk/typescript/src/index.guard.ts @@ -5,7 +5,7 @@ * Generated type guards for "index.ts". * WARNING: Do not manually change this file. */ -import { Ed25519KeypairData, Keypair, PublicKeyInitData, PublicKeyData, SignedTransaction, TransactionResponse, TransferTransaction, TxnDataSerializer, TransactionDigest, SuiAddress, ObjectOwner, ObjectRef, ObjectContentField, ObjectContentFields, ObjectContent, SuiObject, ObjectExistsInfo, ObjectNotExistsInfo, ObjectStatus, ObjectType, GetOwnedObjectRefsResponse, GetObjectInfoResponse, ObjectDigest, ObjectId, SequenceNumber, RawObjectRef, Transfer, RawAuthoritySignInfo, TransactionKindName, SingleTransactionKind, TransactionKind, TransactionData, EpochId, AuthorityQuorumSignInfo, CertifiedTransaction, GasCostSummary, ExecutionStatus, OwnedObjectRef, TransactionEffects, TransactionEffectsResponse, GatewayTxSeqNumber, GetTxnDigestsResponse, MoveModulePublish, Event, StructTag, MoveTypeTag, MoveCall, MoveCallArg, EmptySignInfo, AuthorityName, AuthoritySignature } from "./index"; +import { Ed25519KeypairData, Keypair, PublicKeyInitData, PublicKeyData, SignedTransaction, TransactionResponse, TransferTransaction, TxnDataSerializer, TransactionDigest, SuiAddress, ObjectOwner, ObjectRef, ObjectContentField, ObjectContentFields, ObjectContent, SuiObject, ObjectExistsInfo, ObjectNotExistsInfo, ObjectStatus, ObjectType, GetOwnedObjectRefsResponse, GetObjectInfoResponse, ObjectDigest, ObjectId, SequenceNumber, RawObjectRef, Transfer, RawAuthoritySignInfo, TransactionKindName, SingleTransactionKind, TransactionKind, TransactionData, EpochId, AuthorityQuorumSignInfo, CertifiedTransaction, GasCostSummary, ExecutionStatus, OwnedObjectRef, TransactionEffects, TransactionEffectsResponse, GatewayTxSeqNumber, GetTxnDigestsResponse, MoveModulePublish, Event, StructTag, MoveTypeTag, MoveCall, MoveCallArg, EmptySignInfo, AuthorityName, AuthoritySignature, ExecutionStatusType } from "./index"; import { BN } from "bn.js"; export function isEd25519KeypairData(obj: any, _argumentName?: string): obj is Ed25519KeypairData { @@ -580,3 +580,10 @@ export function isAuthoritySignature(obj: any, _argumentName?: string): obj is A typeof obj === "string" ) } + +export function isExecutionStatusType(obj: any, _argumentName?: string): obj is ExecutionStatusType { + return ( + (obj === "Success" || + obj === "Failure") + ) +} diff --git a/sdk/typescript/src/types/transactions.ts b/sdk/typescript/src/types/transactions.ts index fba7ec61d605c..2bdb2af2123a1 100644 --- a/sdk/typescript/src/types/transactions.ts +++ b/sdk/typescript/src/types/transactions.ts @@ -117,6 +117,7 @@ export type MoveCallArg = export type EmptySignInfo = object; export type AuthorityName = string; export type AuthoritySignature = string; +export type ExecutionStatusType = 'Success' | 'Failure'; /* ---------------------------- Helper functions ---------------------------- */ @@ -153,3 +154,9 @@ export function getTransactionKind( const tx = getSingleTransactionKind(data); return tx && (Object.keys(tx)[0] as TransactionKindName); } + +export function getExecutionStatusType( + data: ExecutionStatus +): ExecutionStatusType { + return Object.keys(data)[0] as ExecutionStatusType; +} \ No newline at end of file From dcdbdcdfaba8db0ced8cc7d015ed91ed2cb2a8ee Mon Sep 17 00:00:00 2001 From: Jibz Date: Mon, 2 May 2022 23:37:26 -0400 Subject: [PATCH 5/6] lint fix --- .../client/src/components/search/Search.tsx | 2 +- .../transaction-card/RecentTxCard.tsx | 2 +- .../TransactionCard.module.css | 4 +- .../transaction-card/TransactionCard.tsx | 73 +++++++++---------- .../transaction-result/TransactionResult.tsx | 2 +- 5 files changed, 41 insertions(+), 42 deletions(-) diff --git a/explorer/client/src/components/search/Search.tsx b/explorer/client/src/components/search/Search.tsx index 2f6f8109bd165..6d948ab853ec0 100644 --- a/explorer/client/src/components/search/Search.tsx +++ b/explorer/client/src/components/search/Search.tsx @@ -18,7 +18,7 @@ function Search() { (e: React.FormEvent) => { e.preventDefault(); // Prevent empty search - if (!input.length) return; + if (!input.length) return; setPleaseWaitMode(true); navigateWithUnknown(input, navigate).then(() => { setInput(''); diff --git a/explorer/client/src/components/transaction-card/RecentTxCard.tsx b/explorer/client/src/components/transaction-card/RecentTxCard.tsx index e746c40e591cd..29fcfbf7e5a78 100644 --- a/explorer/client/src/components/transaction-card/RecentTxCard.tsx +++ b/explorer/client/src/components/transaction-card/RecentTxCard.tsx @@ -8,7 +8,7 @@ import { getSingleTransactionKind, getTransactionKind, getTransferTransaction, - getExecutionStatusType + getExecutionStatusType, } from 'sui.js'; import Longtext from '../../components/longtext/Longtext'; diff --git a/explorer/client/src/components/transaction-card/TransactionCard.module.css b/explorer/client/src/components/transaction-card/TransactionCard.module.css index 998ae743d3639..ef6e4e4ab6bba 100644 --- a/explorer/client/src/components/transaction-card/TransactionCard.module.css +++ b/explorer/client/src/components/transaction-card/TransactionCard.module.css @@ -17,7 +17,7 @@ div.txcardgrid:nth-child(2n + 1) { } div.txcardgrid:first-child { - @apply bg-offblack rounded-none items-center pt-5 pb-5 font-black shadow-md border-2 border-coolGray-400 ; + @apply bg-offblack rounded-none items-center pt-5 pb-5 font-black shadow-md border-2 border-coolGray-400; } div.txcardgrid > div:first-child { @@ -72,6 +72,6 @@ ul.listitems { @apply mt-2 list-none p-0 md:flex grid gap-2 pr-0; } -div.sublist .sublistlabel{ +div.sublist .sublistlabel { @apply w-28 flex-none; } diff --git a/explorer/client/src/components/transaction-card/TransactionCard.tsx b/explorer/client/src/components/transaction-card/TransactionCard.tsx index 7015143816dab..65b83f819f680 100644 --- a/explorer/client/src/components/transaction-card/TransactionCard.tsx +++ b/explorer/client/src/components/transaction-card/TransactionCard.tsx @@ -202,57 +202,56 @@ function TransactionCard({ txdata }: Props) { } key={n} > -
    - {list.map( - ( - sublist: string, - l: number - ) => ( -
    +
    + {list.map( + ( + sublist: string, + l: number + ) => (
    - {itm.subLabel ? ( +
    + {itm.subLabel ? ( +
    + { + itm + .subLabel[ + l + ] + } + + : +
    + ) : ( + '' + )}
    - { - itm - .subLabel[ - l - ] + sublist } - - :
    - ) : ( - '' - )} -
    - { - sublist - }
    -
    - ) - )} + ) + )}
  • ) : ( diff --git a/explorer/client/src/pages/transaction-result/TransactionResult.tsx b/explorer/client/src/pages/transaction-result/TransactionResult.tsx index 4da7131149ebd..075dfc35de6b3 100644 --- a/explorer/client/src/pages/transaction-result/TransactionResult.tsx +++ b/explorer/client/src/pages/transaction-result/TransactionResult.tsx @@ -131,7 +131,7 @@ function TransactionResult() { }); }) .catch((err) => { - console.log(err) + console.log(err); setTxState({ ...initState, loadState: 'fail', From 38c3141c7685d7b5cb45ca39d33b0fe41307c5f1 Mon Sep 17 00:00:00 2001 From: Chris Li <666lcz@gmail.com> Date: Mon, 2 May 2022 23:16:29 -0700 Subject: [PATCH 6/6] Move functions to SDK and add type annotations --- .../transaction-card/RecentTxCard.tsx | 64 ++++++++------- .../transaction-card/TransactionCard.tsx | 13 ++-- .../transaction-result/TransactionResult.tsx | 77 +++++++------------ sdk/typescript/src/index.guard.ts | 35 +++++---- sdk/typescript/src/types/transactions.ts | 41 +++++++++- 5 files changed, 121 insertions(+), 109 deletions(-) diff --git a/explorer/client/src/components/transaction-card/RecentTxCard.tsx b/explorer/client/src/components/transaction-card/RecentTxCard.tsx index 29fcfbf7e5a78..28894f43f5b11 100644 --- a/explorer/client/src/components/transaction-card/RecentTxCard.tsx +++ b/explorer/client/src/components/transaction-card/RecentTxCard.tsx @@ -9,6 +9,7 @@ import { getTransactionKind, getTransferTransaction, getExecutionStatusType, + getTotalGasUsed, } from 'sui.js'; import Longtext from '../../components/longtext/Longtext'; @@ -21,31 +22,28 @@ import type { CertifiedTransaction, GetTxnDigestsResponse, TransactionEffectsResponse, - ExecutionStatus, ExecutionStatusType, + TransactionKindName, } from 'sui.js'; import styles from './RecentTxCard.module.css'; -const initState = { +const initState: { loadState: string; latestTx: TxnData[] } = { loadState: 'pending', latestTx: [], }; -const getGasFeesAndStatus = (txStatusData: ExecutionStatus) => { - const txStatus: ExecutionStatusType = getExecutionStatusType(txStatusData); - const txGasObj = Object.values(txStatusData)[0]; - const txGas = - txGasObj.gas_cost.computation_cost + - txGasObj.gas_cost.storage_cost - - txGasObj.gas_cost.storage_rebate; - return { - txStatus: txStatus.toLocaleLowerCase(), - txGas, - }; +type TxnData = { + To?: string; + seq: number; + txId: string; + status: ExecutionStatusType; + txGas: number; + kind: TransactionKindName | undefined; + From: string; }; -const getRecentTransactions = async (txNum: number) => { +async function getRecentTransactions(txNum: number): Promise { try { // Get the latest transactions // TODO add batch transaction kind TransactionDigest @@ -56,8 +54,9 @@ const getRecentTransactions = async (txNum: number) => { const txLatest = await Promise.all( transactions.map(async (tx) => { + const [seq, digest] = tx; return await rpc - .getTransactionWithEffects(tx[1]) + .getTransactionWithEffects(digest) .then((txEff: TransactionEffectsResponse) => { const res: CertifiedTransaction = txEff.certificate; const singleTransaction = getSingleTransactionKind( @@ -73,17 +72,13 @@ const getRecentTransactions = async (txNum: number) => { res.data )?.recipient; - // TODO use ExecutionStatus and add a method on sui.js to get the gas data - const txStatusData = getGasFeesAndStatus( - txEff.effects.status - ); - // Calculate the gas used - return { - block: tx[0], - txId: tx[1], - success: txStatusData.txStatus, - txGas: txStatusData.txGas, + seq, + txId: digest, + status: getExecutionStatusType( + txEff.effects.status + ), + txGas: getTotalGasUsed(txEff.effects.status), kind: txKind, From: res.data.sender, ...(recipient @@ -96,21 +91,21 @@ const getRecentTransactions = async (txNum: number) => { .catch((err) => { console.error( 'Failed to get transaction details for txn digest', - tx[1], + digest, err ); - return false; + return null; }); }) ); - // Remove failed transactions and sort by block number + // Remove failed transactions and sort by sequence number return txLatest .filter((itm) => itm) - .sort((a: any, b: any) => b.block - a.block); + .sort((a, b) => b!.seq - a!.seq) as TxnData[]; } catch (error) { throw error; } -}; +} function truncate(fullStr: string, strLen: number, separator: string) { if (fullStr.length <= strLen) return fullStr; @@ -192,7 +187,7 @@ function LatestTxCard() {
    Gas
    Sender & Receiver
    - {results.latestTx.map((tx: any, index: number) => ( + {results.latestTx.map((tx, index) => (
    {tx.kind}
    - {tx.success === 'success' ? '✔' : '✖'} + {tx.status === 'Success' ? '✔' : '✖'}
    {tx.txGas}
    diff --git a/explorer/client/src/components/transaction-card/TransactionCard.tsx b/explorer/client/src/components/transaction-card/TransactionCard.tsx index 65b83f819f680..c9fedc3ab47d6 100644 --- a/explorer/client/src/components/transaction-card/TransactionCard.tsx +++ b/explorer/client/src/components/transaction-card/TransactionCard.tsx @@ -16,12 +16,13 @@ import type { CertifiedTransaction, TransactionData, TransactionKindName, + ExecutionStatusType, } from 'sui.js'; import styles from './TransactionCard.module.css'; type TxDataProps = CertifiedTransaction & { - txSuccess: boolean; + status: ExecutionStatusType; gasFee: number; txError: string; }; @@ -41,8 +42,10 @@ function formatTxResponse(tx: TxDataProps, txId: string) { { // May change later label: 'Status', - value: tx.txSuccess ? 'Success' : `Failed - ${tx.txError}`, - classAttr: tx.txSuccess ? 'status-success' : 'status-fail', + value: + tx.status === 'Success' ? 'Success' : `Failed - ${tx.txError}`, + classAttr: + tx.status === 'Success' ? 'status-success' : 'status-fail', }, { label: 'Transaction Type', @@ -137,7 +140,6 @@ function formatByTransactionKind( .map((data: any) => Buffer.from(data['Pure']).toString('base64') ), - // list: true, }, ]; case 'Publish': @@ -147,7 +149,6 @@ function formatByTransactionKind( label: 'Modules', value: publish.modules, list: true, - // sublist: true, }, ]; default: @@ -159,7 +160,7 @@ type Props = { txdata: CertifiedTransaction & { loadState: string; txId: string; - txSuccess: boolean; + status: ExecutionStatusType; gasFee: number; txError: string; }; diff --git a/explorer/client/src/pages/transaction-result/TransactionResult.tsx b/explorer/client/src/pages/transaction-result/TransactionResult.tsx index 075dfc35de6b3..f1e1e206f0889 100644 --- a/explorer/client/src/pages/transaction-result/TransactionResult.tsx +++ b/explorer/client/src/pages/transaction-result/TransactionResult.tsx @@ -4,18 +4,21 @@ import cl from 'classnames'; import { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; -import { getSingleTransactionKind, getExecutionStatusType } from 'sui.js'; +import { + getSingleTransactionKind, + getExecutionStatusType, + getTotalGasUsed, + getExecutionDetails, +} from 'sui.js'; import ErrorResult from '../../components/error-result/ErrorResult'; import TransactionCard from '../../components/transaction-card/TransactionCard'; import theme from '../../styles/theme.module.css'; import { DefaultRpcClient as rpc } from '../../utils/api/DefaultRpcClient'; -import { findDataFromID } from '../../utils/static/searchUtil'; import type { CertifiedTransaction, TransactionEffectsResponse, - ExecutionStatus, ExecutionStatusType, } from 'sui.js'; @@ -24,7 +27,7 @@ import styles from './TransactionResult.module.css'; type TxnState = CertifiedTransaction & { loadState: string; txId: string; - txSuccess: boolean; + status: ExecutionStatusType; gasFee: number; txError: string; }; @@ -50,28 +53,16 @@ const initState: TxnState = { epoch: 0, signatures: [], }, - txSuccess: false, + status: 'Success', gasFee: 0, txError: '', }; -const getGasFeesAndStatus = (txStatusData: ExecutionStatus) => { - const txStatus: ExecutionStatusType = getExecutionStatusType(txStatusData); - const txGasObj = Object.values(txStatusData)[0]; - const txGasFees = - txGasObj.gas_cost.computation_cost + - txGasObj.gas_cost.storage_cost - - txGasObj.gas_cost.storage_rebate; - - return { - txStatus: txStatus.toLowerCase(), - txGas: txGasFees, - }; -}; - -const isStatic = process.env.REACT_APP_DATA !== 'static'; +const useRealData = process.env.REACT_APP_DATA !== 'static'; // if dev fetch data from mock_data.json -function fetchTransactionData(txId: string | undefined) { +function fetchTransactionData( + txId: string | undefined +): Promise { try { if (!txId) { throw new Error('No Txid found'); @@ -79,23 +70,8 @@ function fetchTransactionData(txId: string | undefined) { // add delay to simulate barckend service // Remove this section in production // Use Mockdata in dev - if (!isStatic) { - // resolve after one second - return new Promise((resolve, reject) => { - setTimeout(() => { - const staticObj: CertifiedTransaction = findDataFromID( - txId, - undefined - ); - if (!staticObj) { - reject('txid not found'); - } - resolve({ - certificate: staticObj, - effects: {}, - }); - }, 1000); - }); + if (!useRealData) { + throw new Error('Method not implemented for mock data.'); } return rpc @@ -111,27 +87,29 @@ function TransactionResult() { const [showTxState, setTxState] = useState(initState); useEffect(() => { + if (id == null) { + return; + } fetchTransactionData(id) - .then((txObj: any) => { - const txMeta = getGasFeesAndStatus(txObj.effects.status); + .then((txObj) => { + const executionStatus = txObj.effects.status; + const status = getExecutionStatusType(executionStatus); + const details = getExecutionDetails(executionStatus); + setTxState({ ...txObj.certificate, - txSuccess: txMeta.txStatus === 'success', - gasFee: txMeta.txGas, + status, + gasFee: getTotalGasUsed(executionStatus), txError: - txMeta.txStatus !== 'success' - ? txObj.effects.status.Failure.error[ - Object.keys( - txObj.effects.status.Failure.error - )[0] - ].error + 'error' in details + ? details.error[Object.keys(details.error)[0]].error : '', txId: id, loadState: 'loaded', }); }) .catch((err) => { - console.log(err); + console.log('Error fetching transaction data', err); setTxState({ ...initState, loadState: 'fail', @@ -179,4 +157,3 @@ function TransactionResult() { } export default TransactionResult; -// export { instanceOfDataType }; diff --git a/sdk/typescript/src/index.guard.ts b/sdk/typescript/src/index.guard.ts index 646a72a3b8fc8..13f7b84d0cef4 100644 --- a/sdk/typescript/src/index.guard.ts +++ b/sdk/typescript/src/index.guard.ts @@ -5,7 +5,7 @@ * Generated type guards for "index.ts". * WARNING: Do not manually change this file. */ -import { Ed25519KeypairData, Keypair, PublicKeyInitData, PublicKeyData, SignedTransaction, TransactionResponse, TransferTransaction, TxnDataSerializer, TransactionDigest, SuiAddress, ObjectOwner, ObjectRef, ObjectContentField, ObjectContentFields, ObjectContent, SuiObject, ObjectExistsInfo, ObjectNotExistsInfo, ObjectStatus, ObjectType, GetOwnedObjectRefsResponse, GetObjectInfoResponse, ObjectDigest, ObjectId, SequenceNumber, RawObjectRef, Transfer, RawAuthoritySignInfo, TransactionKindName, SingleTransactionKind, TransactionKind, TransactionData, EpochId, AuthorityQuorumSignInfo, CertifiedTransaction, GasCostSummary, ExecutionStatus, OwnedObjectRef, TransactionEffects, TransactionEffectsResponse, GatewayTxSeqNumber, GetTxnDigestsResponse, MoveModulePublish, Event, StructTag, MoveTypeTag, MoveCall, MoveCallArg, EmptySignInfo, AuthorityName, AuthoritySignature, ExecutionStatusType } from "./index"; +import { Ed25519KeypairData, Keypair, PublicKeyInitData, PublicKeyData, SignedTransaction, TransactionResponse, TransferTransaction, TxnDataSerializer, TransactionDigest, SuiAddress, ObjectOwner, ObjectRef, ObjectContentField, ObjectContentFields, ObjectContent, SuiObject, ObjectExistsInfo, ObjectNotExistsInfo, ObjectStatus, ObjectType, GetOwnedObjectRefsResponse, GetObjectInfoResponse, ObjectDigest, ObjectId, SequenceNumber, RawObjectRef, Transfer, RawAuthoritySignInfo, TransactionKindName, SingleTransactionKind, TransactionKind, TransactionData, EpochId, AuthorityQuorumSignInfo, CertifiedTransaction, GasCostSummary, ExecutionStatusType, ExecutionStatus, ExecutionStatusDetail, OwnedObjectRef, TransactionEffects, TransactionEffectsResponse, GatewayTxSeqNumber, GetTxnDigestsResponse, MoveModulePublish, Event, StructTag, MoveTypeTag, MoveCall, MoveCallArg, EmptySignInfo, AuthorityName, AuthoritySignature } from "./index"; import { BN } from "bn.js"; export function isEd25519KeypairData(obj: any, _argumentName?: string): obj is Ed25519KeypairData { @@ -372,22 +372,32 @@ export function isGasCostSummary(obj: any, _argumentName?: string): obj is GasCo ) } +export function isExecutionStatusType(obj: any, _argumentName?: string): obj is ExecutionStatusType { + return ( + (obj === "Success" || + obj === "Failure") + ) +} + export function isExecutionStatus(obj: any, _argumentName?: string): obj is ExecutionStatus { return ( ((obj !== null && typeof obj === "object" || typeof obj === "function") && - (obj.Success !== null && - typeof obj.Success === "object" || - typeof obj.Success === "function") && - isGasCostSummary(obj.Success.gas_cost) as boolean || + isExecutionStatusDetail(obj.Success) as boolean || (obj !== null && typeof obj === "object" || typeof obj === "function") && - (obj.Failure !== null && - typeof obj.Failure === "object" || - typeof obj.Failure === "function") && - isGasCostSummary(obj.Failure.gas_cost) as boolean) + isExecutionStatusDetail(obj.Failure) as boolean) + ) +} + +export function isExecutionStatusDetail(obj: any, _argumentName?: string): obj is ExecutionStatusDetail { + return ( + (obj !== null && + typeof obj === "object" || + typeof obj === "function") && + isGasCostSummary(obj.gas_cost) as boolean ) } @@ -580,10 +590,3 @@ export function isAuthoritySignature(obj: any, _argumentName?: string): obj is A typeof obj === "string" ) } - -export function isExecutionStatusType(obj: any, _argumentName?: string): obj is ExecutionStatusType { - return ( - (obj === "Success" || - obj === "Failure") - ) -} diff --git a/sdk/typescript/src/types/transactions.ts b/sdk/typescript/src/types/transactions.ts index 2bdb2af2123a1..31e0b1b183af4 100644 --- a/sdk/typescript/src/types/transactions.ts +++ b/sdk/typescript/src/types/transactions.ts @@ -45,9 +45,12 @@ export type GasCostSummary = { storage_rebate: number; }; +export type ExecutionStatusType = 'Success' | 'Failure'; export type ExecutionStatus = - | { Success: { gas_cost: GasCostSummary } } - | { Failure: { gas_cost: GasCostSummary; error: any } }; + | { Success: ExecutionStatusDetail } + | { Failure: ExecutionStatusDetail }; + +export type ExecutionStatusDetail = { gas_cost: GasCostSummary; error?: any }; // TODO: change the tuple to struct from the server end export type OwnedObjectRef = [RawObjectRef, ObjectOwner]; @@ -117,7 +120,6 @@ export type MoveCallArg = export type EmptySignInfo = object; export type AuthorityName = string; export type AuthoritySignature = string; -export type ExecutionStatusType = 'Success' | 'Failure'; /* ---------------------------- Helper functions ---------------------------- */ @@ -159,4 +161,35 @@ export function getExecutionStatusType( data: ExecutionStatus ): ExecutionStatusType { return Object.keys(data)[0] as ExecutionStatusType; -} \ No newline at end of file +} + +export function getGasSummary( + data: ExecutionStatus +): GasCostSummary | undefined { + const details = getExecutionDetails(data); + return details.gas_cost; +} + +export function getTotalGasUsed(data: ExecutionStatus): number { + const gasSummary = getGasSummary(data); + if (gasSummary) { + return ( + gasSummary.computation_cost + + gasSummary.storage_cost - + gasSummary.storage_rebate + ); + } + return 0; +} + +export function getExecutionDetails( + data: ExecutionStatus +): ExecutionStatusDetail { + if ('Success' in data) { + return data.Success; + } else if ('Failure' in data) { + return data.Failure; + } + console.error('Unrecognized ExecutionStatus:', data); + return data[Object.keys(data)[0]]; +}