From 0520137299bdbb150e1cd6b515c52620aa1a97be Mon Sep 17 00:00:00 2001 From: Bran <52735957+brancoder@users.noreply.github.com> Date: Thu, 25 Jan 2024 13:57:02 +0100 Subject: [PATCH] Feat: Improve displaying payloads on Block page (#983) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Add infra to support OutputPage (nova) * feat: add block page for nova * fix: Add initial Block page structure * fix: Add initial Block page structure * feat: Add View for tagged data payload and signed transaction payload * fix: add block payload section and transaction helper * feat: add block transaction metadata tab * fix: format * fix: add max burned mana * fix: improve block with transaction payload * fix: improve validation block * fix: improve candidacy announcement and block page title * fix: lint * fix: remove nested transaction from block payload section * fix: insantiate block using Plain Instance to a class in useBlock --------- Co-authored-by: Mario Sarcevic Co-authored-by: Begoña Álvarez de la Cruz --- .../app/components/nova/ContextInputView.tsx | 52 +++++++ .../block/section/BlockPayloadSection.tsx | 8 +- .../section/TransactionMetadataSection.tsx | 131 +++++++++++------- client/src/app/routes/nova/Block.tsx | 127 +++++++++++------ client/src/helpers/nova/hooks/useBlock.ts | 4 +- 5 files changed, 219 insertions(+), 103 deletions(-) create mode 100644 client/src/app/components/nova/ContextInputView.tsx diff --git a/client/src/app/components/nova/ContextInputView.tsx b/client/src/app/components/nova/ContextInputView.tsx new file mode 100644 index 000000000..ee7c65135 --- /dev/null +++ b/client/src/app/components/nova/ContextInputView.tsx @@ -0,0 +1,52 @@ +import { + BlockIssuanceCreditContextInput, + CommitmentContextInput, + ContextInput, + ContextInputType, + RewardContextInput, +} from "@iota/sdk-wasm-nova/web"; +import React from "react"; +import TruncatedId from "../stardust/TruncatedId"; +import { useNetworkInfoNova } from "~/helpers/nova/networkInfo"; + +interface IContextInputViewProps { + readonly contextInput: ContextInput; +} + +const ContextInputView: React.FC = ({ contextInput }) => { + const { name: network } = useNetworkInfoNova((s) => s.networkInfo); + if (contextInput.type === ContextInputType.COMMITMENT) { + const input = contextInput as CommitmentContextInput; + + return ( +
+
Commitment id
+
{input.commitmentId}
+
+ ); + } else if (contextInput.type === ContextInputType.BLOCK_ISSUANCE_CREDIT) { + const input = contextInput as BlockIssuanceCreditContextInput; + + return ( +
+
Account
+
+ +
+
+ ); + } else if (contextInput.type === ContextInputType.REWARD) { + const input = contextInput as RewardContextInput; + + return ( +
+
Reward Input Index
+
{input.index}
+
+ ); + } + + return null; +}; + +export default ContextInputView; diff --git a/client/src/app/components/nova/block/section/BlockPayloadSection.tsx b/client/src/app/components/nova/block/section/BlockPayloadSection.tsx index 4bfb2791b..6f0dffbfb 100644 --- a/client/src/app/components/nova/block/section/BlockPayloadSection.tsx +++ b/client/src/app/components/nova/block/section/BlockPayloadSection.tsx @@ -20,6 +20,7 @@ interface BlockPayloadSectionProps { const BlockPayloadSection: React.FC = ({ block, inputs, outputs, transferTotal }) => { const payload = (block.body as BasicBlockBody).payload; + if (payload?.type === PayloadType.SignedTransaction && inputs && outputs && transferTotal !== undefined) { const transactionPayload = payload as ISignedTransactionPayload; const transaction = transactionPayload.transaction; @@ -36,13 +37,6 @@ const BlockPayloadSection: React.FC = ({ block, inputs )} ); - } else if (payload?.type === PayloadType.CandidacyAnnouncement) { - return ( -
- {/* todo */} - CandidacyAnnouncement -
- ); } else if (payload?.type === PayloadType.TaggedData) { return (
diff --git a/client/src/app/components/nova/block/section/TransactionMetadataSection.tsx b/client/src/app/components/nova/block/section/TransactionMetadataSection.tsx index 621e4fd03..b1d8e2e19 100644 --- a/client/src/app/components/nova/block/section/TransactionMetadataSection.tsx +++ b/client/src/app/components/nova/block/section/TransactionMetadataSection.tsx @@ -1,68 +1,97 @@ import classNames from "classnames"; -import { TRANSACTION_FAILURE_REASON_STRINGS, TransactionMetadata } from "@iota/sdk-wasm-nova/web"; +import { TRANSACTION_FAILURE_REASON_STRINGS, Transaction, TransactionMetadata } from "@iota/sdk-wasm-nova/web"; import React from "react"; import "./TransactionMetadataSection.scss"; import Spinner from "../../../Spinner"; import TruncatedId from "~/app/components/stardust/TruncatedId"; +import ContextInputView from "../../ContextInputView"; +import { useNetworkInfoNova } from "~/helpers/nova/networkInfo"; interface TransactionMetadataSectionProps { - readonly network: string; - readonly metadata?: TransactionMetadata; + readonly transaction?: Transaction; + readonly transactionMetadata?: TransactionMetadata; readonly metadataError?: string; - readonly isLinksDisabled: boolean; } -const TransactionMetadataSection: React.FC = ({ network, metadata, metadataError, isLinksDisabled }) => ( -
-
- {!metadata && !metadataError && } - {metadataError &&

Failed to retrieve metadata. {metadataError}

} - {metadata && !metadataError && ( - -
-
Transaction Id
-
- -
-
-
-
Transaction Status
-
-
= ({ transaction, transactionMetadata, metadataError }) => { + const { name: network } = useNetworkInfoNova((s) => s.networkInfo); + + return ( +
+
+ {!transactionMetadata && !metadataError && } + {metadataError ? ( +

Failed to retrieve metadata. {metadataError}

+ ) : ( + + {transactionMetadata && ( + <> +
+
Transaction Status
+
+
+ {transactionMetadata.transactionState} +
+
+
+ {transactionMetadata.transactionFailureReason && ( +
+
Failure Reason
+
+ {TRANSACTION_FAILURE_REASON_STRINGS[transactionMetadata.transactionFailureReason]} +
+
+ )} + + )} + {transaction && ( + <> +
+
Creation slot
+
{transaction.creationSlot}
+
+ {transaction?.contextInputs?.map((contextInput, idx) => ( + + ))} + {transaction?.allotments && ( +
+
Mana Allotment Accounts
+ {transaction?.allotments?.map((allotment, idx) => ( +
+ +
+ ))} +
)} - > - {metadata.transactionState} -
-
-
- {metadata.transactionFailureReason && ( -
-
Failure Reason
-
{TRANSACTION_FAILURE_REASON_STRINGS[metadata.transactionFailureReason]}
-
- )} - - )} + + )} + + )} +
-
-); + ); +}; TransactionMetadataSection.defaultProps = { - metadata: undefined, + transactionMetadata: undefined, + transaction: undefined, metadataError: undefined, }; diff --git a/client/src/app/routes/nova/Block.tsx b/client/src/app/routes/nova/Block.tsx index 624d6837b..b940f074c 100644 --- a/client/src/app/routes/nova/Block.tsx +++ b/client/src/app/routes/nova/Block.tsx @@ -1,10 +1,10 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { RouteComponentProps } from "react-router-dom"; import mainHeaderMessage from "~assets/modals/stardust/block/main-header.json"; import metadataInfo from "~assets/modals/stardust/block/metadata.json"; import { useBlock } from "~helpers/nova/hooks/useBlock"; import NotFound from "../../components/NotFound"; -import { BasicBlockBody, BlockBodyType, PayloadType, ValidationBlockBody } from "@iota/sdk-wasm-nova/web"; +import { BasicBlockBody, PayloadType, SignedTransactionPayload, Utils, ValidationBlockBody } from "@iota/sdk-wasm-nova/web"; import Modal from "~/app/components/Modal"; import Spinner from "~/app/components/Spinner"; import TruncatedId from "~/app/components/stardust/TruncatedId"; @@ -39,33 +39,48 @@ const Block: React.FC> = ({ params: { network, blockId }, }, }) => { - const { networkInfo } = useNetworkInfoNova(); + const { tokenInfo } = useNetworkInfoNova((s) => s.networkInfo); const [isFormattedBalance, setIsFormattedBalance] = useState(true); const [block, isLoading, blockError] = useBlock(network, blockId); const [blockMetadata] = useBlockMetadata(network, blockId); const [inputs, outputs, transferTotal] = useInputsAndOutputs(network, block); + const [blockBody, setBlockBody] = useState(); + const [transactionId, setTransactionId] = useState(); + const [pageTitle, setPageTitle] = useState("Block"); - function isBasicBlockBody(body: BasicBlockBody | ValidationBlockBody): body is BasicBlockBody { - return body.type === BlockBodyType.Basic; - } - let blockBody: BasicBlockBody | ValidationBlockBody | undefined; - let pageTitle = "Block"; - - switch (block?.body?.type) { - case BlockBodyType.Basic: { - pageTitle = `Basic ${pageTitle}`; - blockBody = block?.body as BasicBlockBody; - break; - } - case BlockBodyType.Validation: { - pageTitle = `Validation ${pageTitle}`; - blockBody = block?.body as ValidationBlockBody; - break; + function updatePageTitle(type: PayloadType | undefined): void { + let title = null; + switch (type) { + case PayloadType.TaggedData: + title = "Data"; + break; + case PayloadType.SignedTransaction: + title = "Transaction"; + break; + case PayloadType.CandidacyAnnouncement: + title = "Candidacy Announcement"; + break; } - default: { - break; + + if (title) { + setPageTitle(`${title} ${pageTitle}`); } } + useEffect(() => { + if (block?.isBasic()) { + const body = block.body.asBasic(); + setBlockBody(body); + updatePageTitle(body.payload?.type); + + if (body.payload?.type === PayloadType.SignedTransaction) { + const tsxId = Utils.transactionId(body.payload as SignedTransactionPayload); + setTransactionId(tsxId); + } + } else { + setBlockBody(block?.body.asValidation()); + setPageTitle(`Validation ${pageTitle}`); + } + }, [block]); const tabbedSections = []; let idx = 0; @@ -85,9 +100,8 @@ const Block: React.FC> = ({ tabbedSections.push( , ); } @@ -105,6 +119,14 @@ const Block: React.FC> = ({
+ {transactionId && ( +
+
Transaction Id
+
+ +
+
+ )}
Issuing Time
{DateHelper.formatShort(Number(block.header.issuingTime) / 1000000)}
@@ -115,23 +137,23 @@ const Block: React.FC> = ({
+
+
Latest finalized slot
+
{block.header.latestFinalizedSlot}
+
Issuer
-
- +
+
-
{blockBody?.strongParents && (
Strong Parents
{blockBody.strongParents.map((parent, idx) => (
- +
))}
@@ -139,25 +161,42 @@ const Block: React.FC> = ({ {blockBody?.weakParents && (
Weak Parents
- {blockBody.weakParents.map((child, idx) => ( + {blockBody.weakParents.map((weak, idx) => (
- + +
+ ))} +
+ )} + {blockBody?.shallowLikeParents && ( +
+
Shallow Parents
+ {blockBody.shallowLikeParents.map((shallow, idx) => ( +
+
))}
)}
- {blockBody && isBasicBlockBody(blockBody) && ( + {blockBody?.isValidation() && ( +
+
Highest supported protocol version
+
{blockBody.asValidation().highestSupportedVersion}
+
+ )} + {blockBody?.isBasic() && (
- {blockBody.payload?.type === PayloadType.SignedTransaction && transferTotal !== null && ( +
+
Max burned mana
+
{Number(blockBody.asBasic().maxBurnedMana)}
+
+ {blockBody.asBasic().payload?.type === PayloadType.SignedTransaction && transferTotal !== null && (
Amount transacted
setIsFormattedBalance(!isFormattedBalance)} className="pointer margin-r-5"> - {formatAmount(transferTotal, networkInfo.tokenInfo, !isFormattedBalance)} + {formatAmount(transferTotal, tokenInfo, !isFormattedBalance)}
@@ -166,22 +205,22 @@ const Block: React.FC> = ({ { if (isMounted) { - setBlock(response.block ?? null); + const block = plainToInstance(Block, response.block) as unknown as Block; + setBlock(block ?? null); setError(response.error); } })