From 71d9b1361c1907f631f262f5224d4f0597158bd1 Mon Sep 17 00:00:00 2001 From: Mario Sarcevic Date: Fri, 1 Mar 2024 13:45:42 +0100 Subject: [PATCH] feat: Add mana from the actual output for Account/Anchor/Nft addresses to Mana Balance --- .../nova/hooks/useAccountAddressState.ts | 4 +- .../helpers/nova/hooks/useAddressBalance.ts | 41 +++++++++++++++++-- .../nova/hooks/useAnchorAddressState.ts | 6 ++- .../helpers/nova/hooks/useAnchorDetails.ts | 12 ++++-- .../helpers/nova/hooks/useNftAddressState.ts | 6 ++- .../src/helpers/nova/hooks/useNftDetails.ts | 12 ++++-- 6 files changed, 68 insertions(+), 13 deletions(-) diff --git a/client/src/helpers/nova/hooks/useAccountAddressState.ts b/client/src/helpers/nova/hooks/useAccountAddressState.ts index bcacc3011..647cd58fd 100644 --- a/client/src/helpers/nova/hooks/useAccountAddressState.ts +++ b/client/src/helpers/nova/hooks/useAccountAddressState.ts @@ -94,13 +94,15 @@ export const useAccountAddressState = (address: AccountAddress): [IAccountAddres ); const { accountOutput, accountOutputMetadata, isLoading: isAccountDetailsLoading } = useAccountDetails(network, address.accountId); + const { manaRewards } = useOutputManaRewards(network, accountOutputMetadata?.outputId ?? ""); const { totalBaseTokenBalance, availableBaseTokenBalance, totalManaBalance, availableManaBalance } = useAddressBalance( network, state.addressDetails, accountOutput, + accountOutputMetadata, + manaRewards, ); - const { manaRewards } = useOutputManaRewards(network, accountOutputMetadata?.outputId ?? ""); const [addressBasicOutputs, isBasicOutputsLoading] = useAddressBasicOutputs(network, state.addressDetails?.bech32 ?? null); const [addressNftOutputs, isNftOutputsLoading] = useAddressNftOutputs(network, state.addressDetails?.bech32 ?? null); const [foundries, isFoundriesLoading] = useAccountControlledFoundries(network, state.addressDetails); diff --git a/client/src/helpers/nova/hooks/useAddressBalance.ts b/client/src/helpers/nova/hooks/useAddressBalance.ts index 8260a6d3e..e5335cd0f 100644 --- a/client/src/helpers/nova/hooks/useAddressBalance.ts +++ b/client/src/helpers/nova/hooks/useAddressBalance.ts @@ -1,11 +1,13 @@ -import { AddressType, NftOutput, AccountOutput, AnchorOutput } from "@iota/sdk-wasm-nova/web"; +import { AddressType, NftOutput, AccountOutput, AnchorOutput, IOutputMetadataResponse, ManaRewardsResponse } from "@iota/sdk-wasm-nova/web"; import { useEffect, useState } from "react"; import { IManaBalance } from "~/models/api/nova/address/IAddressBalanceResponse"; import { IAddressDetails } from "~/models/api/nova/IAddressDetails"; import { NovaApiClient } from "~/services/nova/novaApiClient"; import { ServiceFactory } from "~factories/serviceFactory"; import { useIsMounted } from "~helpers/hooks/useIsMounted"; +import { useNetworkInfoNova } from "~/helpers/nova/networkInfo"; import { NOVA } from "~models/config/protocolVersion"; +import { buildManaDetailsForOutput } from "../manaUtils"; /** * Fetch the address balance from chronicle nova. @@ -18,6 +20,8 @@ export function useAddressBalance( network: string, addressDetails: IAddressDetails | null, output: AccountOutput | NftOutput | AnchorOutput | null, + outputMetadata?: IOutputMetadataResponse | null, + outputManaRewards?: ManaRewardsResponse | null, ): { totalBaseTokenBalance: number | null; availableBaseTokenBalance: number | null; @@ -26,6 +30,7 @@ export function useAddressBalance( isLoading: boolean; } { const isMounted = useIsMounted(); + const { protocolInfo, latestConfirmedSlot } = useNetworkInfoNova((s) => s.networkInfo); const [apiClient] = useState(ServiceFactory.get(`api-client-${NOVA}`)); const [totalBaseTokenBalance, setTotalBaseTokenBalance] = useState(null); const [availableBaseTokenBalance, setAvailableBaseTokenBalance] = useState(null); @@ -40,7 +45,7 @@ export function useAddressBalance( addressDetails?.type === AddressType.Account || addressDetails?.type === AddressType.Nft || addressDetails?.type === AddressType.Anchor; - const canLoad = address && (!needsOutputToProceed || (needsOutputToProceed && output !== null)); + const canLoad = address && (!needsOutputToProceed || (needsOutputToProceed && output !== null && outputMetadata)); if (canLoad) { // eslint-disable-next-line no-void @@ -50,12 +55,40 @@ export function useAddressBalance( if (isMounted) { let totalBalance = response?.totalBalance?.amount ?? 0; let availableBalance = response?.availableBalance?.amount ?? 0; - const totalManaBalance = response?.totalBalance?.mana ?? null; - const availableManaBalance = response?.availableBalance?.mana ?? null; + let totalManaBalance = response?.totalBalance?.mana ?? null; + let availableManaBalance = response?.availableBalance?.mana ?? null; if (output) { totalBalance = Number(totalBalance) + Number(output.amount); availableBalance = Number(availableBalance) + Number(output.amount); + + // Output mana + const { included, spent } = outputMetadata ?? {}; + const createdSlotIndex = (included?.slot as number) ?? null; + const spentSlotIndex = (spent?.slot as number) ?? null; + + if (output && createdSlotIndex && protocolInfo) { + const untilSlotIndex = spentSlotIndex ? spentSlotIndex : latestConfirmedSlot > 0 ? latestConfirmedSlot : null; + const outputManaDetails = untilSlotIndex + ? buildManaDetailsForOutput( + output, + createdSlotIndex, + untilSlotIndex, + protocolInfo.parameters, + outputManaRewards ?? null, + ) + : null; + + totalManaBalance = { + stored: (totalManaBalance?.stored ?? 0) + Number(outputManaDetails?.storedMana ?? 0), + potential: (totalManaBalance?.potential ?? 0) + Number(outputManaDetails?.potentialMana ?? 0), + }; + + availableManaBalance = { + stored: (availableManaBalance?.stored ?? 0) + Number(outputManaDetails?.storedMana ?? 0), + potential: (availableManaBalance?.potential ?? 0) + Number(outputManaDetails?.potentialMana ?? 0), + }; + } } setTotalBaseTokenBalance(totalBalance); diff --git a/client/src/helpers/nova/hooks/useAnchorAddressState.ts b/client/src/helpers/nova/hooks/useAnchorAddressState.ts index b8fd4c37f..b4b3d000c 100644 --- a/client/src/helpers/nova/hooks/useAnchorAddressState.ts +++ b/client/src/helpers/nova/hooks/useAnchorAddressState.ts @@ -10,6 +10,7 @@ import { useAddressBalance } from "./useAddressBalance"; import { useAddressBasicOutputs } from "~/helpers/nova/hooks/useAddressBasicOutputs"; import { useAddressNftOutputs } from "~/helpers/nova/hooks/useAddressNftOutputs"; import { IManaBalance } from "~/models/api/nova/address/IAddressBalanceResponse"; +import { useOutputManaRewards } from "./useOutputManaRewards"; export interface IAnchorAddressState { addressDetails: IAddressDetails | null; @@ -61,11 +62,14 @@ export const useAnchorAddressState = (address: AnchorAddress): [IAnchorAddressSt initialState, ); - const { anchorOutput, isLoading: isAnchorDetailsLoading } = useAnchorDetails(network, address.anchorId); + const { anchorOutput, anchorOutputMetadata, isLoading: isAnchorDetailsLoading } = useAnchorDetails(network, address.anchorId); + const { manaRewards } = useOutputManaRewards(network, anchorOutputMetadata?.outputId ?? ""); const { totalBaseTokenBalance, availableBaseTokenBalance, totalManaBalance, availableManaBalance } = useAddressBalance( network, state.addressDetails, anchorOutput, + anchorOutputMetadata, + manaRewards, ); const [addressBasicOutputs, isBasicOutputsLoading] = useAddressBasicOutputs(network, state.addressDetails?.bech32 ?? null); const [addressNftOutputs, isNftOutputsLoading] = useAddressNftOutputs(network, state.addressDetails?.bech32 ?? null); diff --git a/client/src/helpers/nova/hooks/useAnchorDetails.ts b/client/src/helpers/nova/hooks/useAnchorDetails.ts index 3c88c281a..27cbfe40f 100644 --- a/client/src/helpers/nova/hooks/useAnchorDetails.ts +++ b/client/src/helpers/nova/hooks/useAnchorDetails.ts @@ -1,4 +1,4 @@ -import { AnchorOutput } from "@iota/sdk-wasm-nova/web"; +import { AnchorOutput, IOutputMetadataResponse } from "@iota/sdk-wasm-nova/web"; import { useEffect, useState } from "react"; import { ServiceFactory } from "~/factories/serviceFactory"; import { useIsMounted } from "~/helpers/hooks/useIsMounted"; @@ -12,10 +12,14 @@ import { NovaApiClient } from "~/services/nova/novaApiClient"; * @param anchorID The anchor id * @returns The output response and loading bool. */ -export function useAnchorDetails(network: string, anchorId: string | null): { anchorOutput: AnchorOutput | null; isLoading: boolean } { +export function useAnchorDetails( + network: string, + anchorId: string | null, +): { anchorOutput: AnchorOutput | null; anchorOutputMetadata: IOutputMetadataResponse | null; isLoading: boolean } { const isMounted = useIsMounted(); const [apiClient] = useState(ServiceFactory.get(`api-client-${NOVA}`)); const [anchorOutput, setAnchorOutput] = useState(null); + const [anchorOutputMetadata, setAnchorOutputMetadata] = useState(null); const [isLoading, setIsLoading] = useState(true); useEffect(() => { @@ -31,8 +35,10 @@ export function useAnchorDetails(network: string, anchorId: string | null): { an .then((response) => { if (!response?.error && isMounted) { const output = response.anchorOutputDetails?.output as AnchorOutput; + const metadata = response.anchorOutputDetails?.metadata ?? null; setAnchorOutput(output); + setAnchorOutputMetadata(metadata); } }) .finally(() => { @@ -44,5 +50,5 @@ export function useAnchorDetails(network: string, anchorId: string | null): { an } }, [network, anchorId]); - return { anchorOutput, isLoading }; + return { anchorOutput, anchorOutputMetadata, isLoading }; } diff --git a/client/src/helpers/nova/hooks/useNftAddressState.ts b/client/src/helpers/nova/hooks/useNftAddressState.ts index 7be501ed1..15de741cc 100644 --- a/client/src/helpers/nova/hooks/useNftAddressState.ts +++ b/client/src/helpers/nova/hooks/useNftAddressState.ts @@ -10,6 +10,7 @@ import { useAddressBalance } from "./useAddressBalance"; import { useAddressBasicOutputs } from "~/helpers/nova/hooks/useAddressBasicOutputs"; import { useAddressNftOutputs } from "~/helpers/nova/hooks/useAddressNftOutputs"; import { IManaBalance } from "~/models/api/nova/address/IAddressBalanceResponse"; +import { useOutputManaRewards } from "./useOutputManaRewards"; export interface INftAddressState { addressDetails: IAddressDetails | null; @@ -61,11 +62,14 @@ export const useNftAddressState = (address: NftAddress): [INftAddressState, Reac initialState, ); - const { nftOutput, isLoading: isNftDetailsLoading } = useNftDetails(network, address.nftId); + const { nftOutput, nftOutputMetadata, isLoading: isNftDetailsLoading } = useNftDetails(network, address.nftId); + const { manaRewards } = useOutputManaRewards(network, nftOutputMetadata?.outputId ?? ""); const { totalBaseTokenBalance, availableBaseTokenBalance, totalManaBalance, availableManaBalance } = useAddressBalance( network, state.addressDetails, nftOutput, + nftOutputMetadata, + manaRewards, ); const [addressBasicOutputs, isBasicOutputsLoading] = useAddressBasicOutputs(network, state.addressDetails?.bech32 ?? null); const [addressNftOutputs, isNftOutputsLoading] = useAddressNftOutputs(network, state.addressDetails?.bech32 ?? null); diff --git a/client/src/helpers/nova/hooks/useNftDetails.ts b/client/src/helpers/nova/hooks/useNftDetails.ts index 9335864d1..5c01252d7 100644 --- a/client/src/helpers/nova/hooks/useNftDetails.ts +++ b/client/src/helpers/nova/hooks/useNftDetails.ts @@ -1,4 +1,4 @@ -import { NftOutput } from "@iota/sdk-wasm-nova/web"; +import { IOutputMetadataResponse, NftOutput } from "@iota/sdk-wasm-nova/web"; import { useEffect, useState } from "react"; import { ServiceFactory } from "~/factories/serviceFactory"; import { useIsMounted } from "~/helpers/hooks/useIsMounted"; @@ -12,10 +12,14 @@ import { NovaApiClient } from "~/services/nova/novaApiClient"; * @param nftID The nft id * @returns The output response and loading bool. */ -export function useNftDetails(network: string, nftId: string | null): { nftOutput: NftOutput | null; isLoading: boolean } { +export function useNftDetails( + network: string, + nftId: string | null, +): { nftOutput: NftOutput | null; nftOutputMetadata: IOutputMetadataResponse | null; isLoading: boolean } { const isMounted = useIsMounted(); const [apiClient] = useState(ServiceFactory.get(`api-client-${NOVA}`)); const [nftOutput, setNftOutput] = useState(null); + const [nftOutputMetadata, setNftOutputMetadata] = useState(null); const [isLoading, setIsLoading] = useState(true); useEffect(() => { @@ -31,8 +35,10 @@ export function useNftDetails(network: string, nftId: string | null): { nftOutpu .then((response) => { if (!response?.error && isMounted) { const output = response.nftOutputDetails?.output as NftOutput; + const metadata = response.nftOutputDetails?.metadata ?? null; setNftOutput(output); + setNftOutputMetadata(metadata); } }) .finally(() => { @@ -44,5 +50,5 @@ export function useNftDetails(network: string, nftId: string | null): { nftOutpu } }, [network, nftId]); - return { nftOutput, isLoading }; + return { nftOutput, nftOutputMetadata, isLoading }; }