From 80bcd32f4f023b75b605d89587e6326fabb5390e Mon Sep 17 00:00:00 2001 From: Mario Sarcevic Date: Thu, 25 Jan 2024 16:03:38 +0100 Subject: [PATCH] feat: Add mana details to metadata section of Output page (stored with decay, potential, total) for spent and unspent outputs --- client/src/app/AppUtils.tsx | 2 + client/src/app/components/nova/OutputView.tsx | 6 +-- client/src/app/routes/nova/OutputPage.tsx | 54 ++++++++++++++++--- client/src/helpers/nova/manaUtils.ts | 27 ++++++++++ client/src/helpers/nova/networkInfo.ts | 6 ++- 5 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 client/src/helpers/nova/manaUtils.ts diff --git a/client/src/app/AppUtils.tsx b/client/src/app/AppUtils.tsx index 0f240c6a2..6bb7207db 100644 --- a/client/src/app/AppUtils.tsx +++ b/client/src/app/AppUtils.tsx @@ -40,6 +40,8 @@ export const populateNetworkInfoNova = (networkName: string) => { name: networkName, tokenInfo: nodeInfo?.baseToken ?? {}, protocolVersion: protocolInfo?.parameters.version ?? -1, + protocolInfo, + latestConfirmedSlot: nodeInfo?.status?.latestConfirmedBlockSlot ?? -1, bech32Hrp: protocolInfo?.parameters.bech32Hrp ?? "", }); } diff --git a/client/src/app/components/nova/OutputView.tsx b/client/src/app/components/nova/OutputView.tsx index d138f6deb..b63544e7b 100644 --- a/client/src/app/components/nova/OutputView.tsx +++ b/client/src/app/components/nova/OutputView.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; import DropdownIcon from "~assets/dropdown-arrow.svg?react"; import classNames from "classnames"; import { @@ -34,8 +34,8 @@ interface OutputViewProps { } const OutputView: React.FC = ({ outputId, output, showCopyAmount, isPreExpanded, isLinksDisabled }) => { - const [isExpanded, setIsExpanded] = React.useState(isPreExpanded ?? false); - const [isFormattedBalance, setIsFormattedBalance] = React.useState(true); + const [isExpanded, setIsExpanded] = useState(isPreExpanded ?? false); + const [isFormattedBalance, setIsFormattedBalance] = useState(true); const { bech32Hrp, name: network } = useNetworkInfoNova((s) => s.networkInfo); const aliasOrNftBech32 = buildAddressForAliasOrNft(outputId, output, bech32Hrp); diff --git a/client/src/app/routes/nova/OutputPage.tsx b/client/src/app/routes/nova/OutputPage.tsx index 918ff3c8a..8620ec3b9 100644 --- a/client/src/app/routes/nova/OutputPage.tsx +++ b/client/src/app/routes/nova/OutputPage.tsx @@ -7,6 +7,8 @@ import OutputView from "~/app/components/nova/OutputView"; import { useOutputDetails } from "~/helpers/nova/hooks/useOutputDetails"; import CopyButton from "~/app/components/CopyButton"; import TruncatedId from "~/app/components/stardust/TruncatedId"; +import { useNetworkInfoNova } from "~/helpers/nova/networkInfo"; +import { buildManaDetailsForOutput, OutputManaDetails } from "~/helpers/nova/manaUtils"; import "./OutputPage.scss"; interface OutputPageProps { @@ -27,6 +29,7 @@ const OutputPage: React.FC> = ({ }, }) => { const { output, outputMetadataResponse, error } = useOutputDetails(network, outputId); + const { protocolInfo, latestConfirmedSlot } = useNetworkInfoNova((s) => s.networkInfo); if (error) { return ( @@ -47,6 +50,20 @@ const OutputPage: React.FC> = ({ const { blockId, transactionId, outputIndex, isSpent, transactionIdSpent } = outputMetadataResponse ?? {}; + // @ts-expect-error TODO: Remove this ignore once included/spent are honoured in the type https://github.com/iotaledger/iota-sdk/issues/1884 + const createdSlotIndex = (outputMetadataResponse?.included?.slot as number) ?? null; + // @ts-expect-error TODO: Remove this ignore once included/spent are honoured in the type https://github.com/iotaledger/iota-sdk/issues/1884 + const spentSlotIndex = (outputMetadataResponse?.spent?.slot as number) ?? null; + + let outputManaDetails: OutputManaDetails | null = null; + if (output !== null && createdSlotIndex !== null && protocolInfo !== null) { + if (isSpent && spentSlotIndex !== null) { + outputManaDetails = buildManaDetailsForOutput(output, createdSlotIndex, spentSlotIndex, protocolInfo.parameters); + } else if (latestConfirmedSlot > 0) { + outputManaDetails = buildManaDetailsForOutput(output, createdSlotIndex, latestConfirmedSlot, protocolInfo.parameters); + } + } + return ( (output && (
@@ -60,13 +77,7 @@ const OutputPage: React.FC> = ({
- +
@@ -125,6 +136,35 @@ const OutputPage: React.FC> = ({
)} + + {outputManaDetails && ( + <> +
+
Stored mana
+
+ {outputManaDetails.storedMana} +
+
+
+
Stored mana (decayed)
+
+ {outputManaDetails.storedManaDecayed} +
+
+
+
Potential mana
+
+ {outputManaDetails.potentialMana} +
+
+
+
Total mana
+
+ {outputManaDetails.totalMana} +
+
+ + )} diff --git a/client/src/helpers/nova/manaUtils.ts b/client/src/helpers/nova/manaUtils.ts new file mode 100644 index 000000000..62fb9dcb5 --- /dev/null +++ b/client/src/helpers/nova/manaUtils.ts @@ -0,0 +1,27 @@ +import { BasicOutput, Output, ProtocolParameters, Utils } from "@iota/sdk-wasm-nova/web"; + +export interface OutputManaDetails { + storedMana: string; + storedManaDecayed: string; + potentialMana: string; + totalMana: string; +} + +export function buildManaDetailsForOutput( + output: Output, + createdSlotIndex: number, + spentOrLatestSlotIndex: number, + protocolParameters: ProtocolParameters, +): OutputManaDetails { + const decayedMana = Utils.outputManaWithDecay(output, createdSlotIndex, spentOrLatestSlotIndex, protocolParameters); + const storedManaDecayed = BigInt(decayedMana.stored).toString(); + const potentialMana = BigInt(decayedMana.potential).toString(); + const totalMana = BigInt(decayedMana.stored) + BigInt(decayedMana.potential); + + return { + storedMana: (output as BasicOutput).mana?.toString(), + storedManaDecayed, + potentialMana, + totalMana: totalMana.toString(), + }; +} diff --git a/client/src/helpers/nova/networkInfo.ts b/client/src/helpers/nova/networkInfo.ts index 5685ee187..f0cc5944f 100644 --- a/client/src/helpers/nova/networkInfo.ts +++ b/client/src/helpers/nova/networkInfo.ts @@ -1,10 +1,12 @@ -import { INodeInfoBaseToken } from "@iota/sdk-wasm-nova/web"; +import { INodeInfoBaseToken, ProtocolInfo } from "@iota/sdk-wasm-nova/web"; import { create } from "zustand"; interface INetworkInfo { name: string; tokenInfo: INodeInfoBaseToken; protocolVersion: number; + protocolInfo: ProtocolInfo | null; + latestConfirmedSlot: number; bech32Hrp: string; } @@ -25,6 +27,8 @@ export const useNetworkInfoNova = create((set) => ({ useMetricPrefix: true, }, protocolVersion: -1, + protocolInfo: null, + latestConfirmedSlot: -1, bech32Hrp: "", }, setNetworkInfo: (networkInfo) => {