Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: incorrect balance on alias address #1033

Merged
merged 13 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions client/src/app/components/stardust/address/AddressBalance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ interface AddressBalanceProps {
/**
* The storage rent balance.
*/
readonly storageRentBalance: number | null;
readonly storageDeposit: number | null;
}

const CONDITIONAL_BALANCE_INFO =
"These funds reside within outputs with additional unlock conditions which might be potentially un-lockable";

const AddressBalance: React.FC<AddressBalanceProps> = ({ balance, spendableBalance, storageRentBalance }) => {
const AddressBalance: React.FC<AddressBalanceProps> = ({ balance, spendableBalance, storageDeposit }) => {
const { name: network, tokenInfo } = useContext(NetworkContext);
const [formatBalanceFull, setFormatBalanceFull] = useState(false);
const [formatConditionalBalanceFull, setFormatConditionalBalanceFull] = useState(false);
Expand Down Expand Up @@ -101,14 +101,7 @@ const AddressBalance: React.FC<AddressBalanceProps> = ({ balance, spendableBalan
false,
conditionalBalance,
)}
{buildBalanceView(
"Storage Deposit",
formatStorageBalanceFull,
setFormatStorageBalanceFull,
false,
false,
storageRentBalance,
)}
{buildBalanceView("Storage Deposit", formatStorageBalanceFull, setFormatStorageBalanceFull, false, false, storageDeposit)}
</div>
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions client/src/app/routes/stardust/AddressPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const AddressPage: React.FC<RouteComponentProps<AddressRouteProps>> = ({
bech32AddressDetails,
balance,
availableBalance,
storageRentBalance,
storageDeposit,
isBasicOutputsLoading,
isAliasOutputsLoading,
isNftOutputsLoading,
Expand Down Expand Up @@ -88,7 +88,7 @@ const AddressPage: React.FC<RouteComponentProps<AddressRouteProps>> = ({
<AddressBalance
balance={balance}
spendableBalance={availableBalance}
storageRentBalance={storageRentBalance}
storageDeposit={storageDeposit}
/>
)}
</div>
Expand Down
50 changes: 28 additions & 22 deletions client/src/app/routes/stardust/AddressState.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { Bech32Helper } from "@iota/iota.js";
import {
HexEncodedString,
AddressType,
AliasOutput,
BasicOutput,
FeatureType,
HexEncodedString,
MetadataFeature,
Output,
OutputResponse,
OutputType,
AddressType,
Output,
BasicOutput,
FeatureType,
} from "@iota/sdk-wasm/web";
import { Reducer, useContext, useEffect, useReducer } from "react";
import { useLocation, useParams } from "react-router-dom";
import { useAliasContainsDID } from "~/helpers/hooks/useAliasContainsDID";
import { useResolvedDID } from "~/helpers/hooks/useResolvedDID";
import { IDIDResolverResponse } from "~/models/api/IDIDResolverResponse";
import { useAddressAliasOutputs } from "~helpers/hooks/useAddressAliasOutputs";
import { useAddressBalance } from "~helpers/hooks/useAddressBalance";
import { useAddressBasicOutputs } from "~helpers/hooks/useAddressBasicOutputs";
Expand All @@ -30,15 +33,12 @@ import { IBech32AddressDetails } from "~models/api/IBech32AddressDetails";
import { IParticipation } from "~models/api/stardust/participation/IParticipation";
import NetworkContext from "../../context/NetworkContext";
import { AddressRouteProps } from "../AddressRouteProps";
import { useAliasContainsDID } from "~/helpers/hooks/useAliasContainsDID";
import { useResolvedDID } from "~/helpers/hooks/useResolvedDID";
import { IDIDResolverResponse } from "~/models/api/IDIDResolverResponse";

export interface IAddressState {
bech32AddressDetails: IBech32AddressDetails | null;
balance: number | null;
availableBalance: number | null;
storageRentBalance: number | null;
storageDeposit: number | null;
addressOutputs: OutputResponse[] | null;
addressBasicOutputs: OutputResponse[] | null;
isBasicOutputsLoading: boolean;
Expand Down Expand Up @@ -70,7 +70,7 @@ const initialState = {
bech32AddressDetails: null,
balance: null,
availableBalance: null,
storageRentBalance: null,
storageDeposit: null,
addressOutputs: null,
addressBasicOutputs: null,
isBasicOutputsLoading: true,
Expand Down Expand Up @@ -121,13 +121,16 @@ export const useAddressPageState = (): [IAddressState, React.Dispatch<Partial<IA
const [addressBasicOutputs, isBasicOutputsLoading] = useAddressBasicOutputs(network, addressBech32);
const [addressAliasOutputs, isAliasOutputsLoading] = useAddressAliasOutputs(network, addressBech32);
const [addressNftOutputs, isNftOutputsLoading] = useAddressNftOutputs(network, addressBech32);
const [, nftMetadata, issuerId, isNftDetailsLoading] = useNftDetails(network, addressType === AddressType.Nft ? addressHex : null);
const [nftOutput, nftMetadata, issuerId, isNftDetailsLoading] = useNftDetails(
network,
addressType === AddressType.Nft ? addressHex : null,
);
const [aliasOutput, isAliasDetailsLoading] = useAliasDetails(network, addressType === AddressType.Alias ? addressHex : null);
const [aliasFoundries, isAliasFoundriesLoading] = useAliasControlledFoundries(
network,
addressType === AddressType.Alias ? state.bech32AddressDetails : null,
);
const [balance, availableBalance] = useAddressBalance(network, state.bech32AddressDetails?.bech32 ?? null);
const [balance, availableBalance] = useAddressBalance(network, state.bech32AddressDetails, aliasOutput ?? nftOutput ?? null);
const [eventDetails] = useParticipationEventDetails(state.participations ?? undefined);

const [aliasContainsDID] = useAliasContainsDID(aliasOutput);
Expand Down Expand Up @@ -197,16 +200,19 @@ export const useAddressPageState = (): [IAddressState, React.Dispatch<Partial<IA
]);

useEffect(() => {
if (addressBasicOutputs && addressAliasOutputs && addressNftOutputs) {
const mergedOutputResponses = [...addressBasicOutputs, ...addressAliasOutputs, ...addressNftOutputs];
const outputs = mergedOutputResponses.map<Output>((or) => or.output);
const storageRentBalanceUpdate = TransactionsHelper.computeStorageRentBalance(outputs, rentStructure);

setState({
addressOutputs: mergedOutputResponses,
storageRentBalance: storageRentBalanceUpdate,
});
const addressOutputs =
[...(addressBasicOutputs ?? []), ...(addressAliasOutputs ?? []), ...(addressNftOutputs ?? [])].filter((o) => o !== null) ?? [];
let outputsComputedInStorageDeposit = addressOutputs?.map<Output>((or) => or.output);
const addressOutputItself = nftOutput ?? aliasOutput;
if (addressOutputItself) {
outputsComputedInStorageDeposit = [...outputsComputedInStorageDeposit, addressOutputItself];
}
const storageDeposit = TransactionsHelper.computeStorageDeposit(outputsComputedInStorageDeposit, rentStructure);

setState({
addressOutputs,
storageDeposit,
});
if (addressBasicOutputs && !state.participations) {
let foundParticipations: IParticipation[] = [];
for (const outputResponse of addressBasicOutputs) {
Expand All @@ -231,7 +237,7 @@ export const useAddressPageState = (): [IAddressState, React.Dispatch<Partial<IA
});
}
}
}, [addressBasicOutputs, addressAliasOutputs, addressNftOutputs]);
}, [addressBasicOutputs, addressAliasOutputs, addressNftOutputs, aliasOutput, nftOutput]);

return [state, setState];
};
34 changes: 27 additions & 7 deletions client/src/helpers/hooks/useAddressBalance.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import { AddressType, AliasOutput, NftOutput } from "@iota/sdk-wasm/web";
import { useEffect, useState } from "react";
import { useIsMounted } from "./useIsMounted";
import { IBech32AddressDetails } from "~/models/api/IBech32AddressDetails";
import { ServiceFactory } from "~factories/serviceFactory";
import { STARDUST } from "~models/config/protocolVersion";
import { StardustApiClient } from "~services/stardust/stardustApiClient";
import { useIsMounted } from "./useIsMounted";

/**
* Fetch the address balance
* @param network The Network in context
* @param address The bech32 address
* @param output The output wrapping the address, used to add the output amount to the balance
* @returns The address balance, signature locked balance and a loading bool.
*/
export function useAddressBalance(network: string, address: string | null): [number | null, number | null, boolean] {
export function useAddressBalance(
network: string,
addressDetails: IBech32AddressDetails | null,
output: AliasOutput | NftOutput | null,
): [number | null, number | null, boolean] {
const isMounted = useIsMounted();
const [apiClient] = useState(ServiceFactory.get<StardustApiClient>(`api-client-${STARDUST}`));
const [balance, setBalance] = useState<number | null>(null);
Expand All @@ -19,28 +26,41 @@ export function useAddressBalance(network: string, address: string | null): [num

useEffect(() => {
setIsLoading(true);
if (address) {
const address = addressDetails?.bech32;
const needsOutputToProceed = addressDetails?.type === AddressType.Alias || addressDetails?.type === AddressType.Nft;
const canLoad = address && (!needsOutputToProceed || (needsOutputToProceed && output));
if (canLoad) {
// eslint-disable-next-line no-void
void (async () => {
const response = await apiClient.addressBalanceChronicle({ network, address });

if (response?.totalBalance !== undefined && isMounted) {
setBalance(response.totalBalance);
setAvailableBalance(response.availableBalance ?? null);
let totalBalance = response.totalBalance;
let availableBalance = response.availableBalance ?? 0;
if (output) {
totalBalance = Number(totalBalance) + Number(output.amount);
availableBalance = Number(availableBalance) + Number(output.amount);
}
setBalance(totalBalance);
setAvailableBalance(availableBalance > 0 ? availableBalance : null);
} else if (isMounted) {
// Fallback balance from iotajs (node)
const addressDetailsWithBalance = await apiClient.addressBalance({ network, address });

if (addressDetailsWithBalance && isMounted) {
setBalance(Number(addressDetailsWithBalance.balance));
let totalBalance = Number(addressDetailsWithBalance.balance);
if (output) {
totalBalance = Number(totalBalance) + Number(output.amount);
}
setBalance(totalBalance);
setAvailableBalance(null);
}
}
})();
} else {
setIsLoading(false);
}
}, [network, address]);
}, [network, addressDetails, output]);

return [balance, availableBalance, isLoading];
}
2 changes: 1 addition & 1 deletion client/src/helpers/stardust/transactionsHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ export class TransactionsHelper {
return HexHelper.toBigInt256(nftId).eq(bigInt.zero) ? Utils.computeNftId(outputId) : nftId;
}

public static computeStorageRentBalance(outputs: Output[], rentStructure: IRent): number {
public static computeStorageDeposit(outputs: Output[], rentStructure: IRent): number {
const outputsWithoutSdruc = outputs.filter((output) => {
if (output.type === OutputType.Treasury) {
return false;
Expand Down
Loading