diff --git a/frontend/src/lib/components/portfolio/TotalAssetsCard.svelte b/frontend/src/lib/components/portfolio/TotalAssetsCard.svelte index 63c7671c295..f62d23161a0 100644 --- a/frontend/src/lib/components/portfolio/TotalAssetsCard.svelte +++ b/frontend/src/lib/components/portfolio/TotalAssetsCard.svelte @@ -9,7 +9,7 @@ import { formatNumber } from "$lib/utils/format.utils"; import { isNullish, nonNullish } from "@dfinity/utils"; - export let usdAmount: number | undefined = 14500; + export let usdAmount: number | undefined; export let hasUnpricedTokens: boolean = false; let hasError: boolean; diff --git a/frontend/src/lib/services/accounts-balances.services.ts b/frontend/src/lib/services/accounts-balances.services.ts new file mode 100644 index 00000000000..535526a44c4 --- /dev/null +++ b/frontend/src/lib/services/accounts-balances.services.ts @@ -0,0 +1,118 @@ +import { CKBTC_ADDITIONAL_CANISTERS } from "$lib/constants/ckbtc-additional-canister-ids.constants"; +import type { IcrcCanistersStoreData } from "$lib/derived/icrc-canisters.derived"; +import type { SnsFullProject } from "$lib/derived/sns/sns-projects.derived"; +import type { Universe } from "$lib/types/universe"; +import { isArrayEmpty } from "$lib/utils/utils"; +import type { CanisterIdString } from "@dfinity/nns"; +import { Principal } from "@dfinity/principal"; +import { nonNullish } from "@dfinity/utils"; +import { updateBalance } from "./ckbtc-minter.services"; +import { uncertifiedLoadSnsesAccountsBalances } from "./sns-accounts-balance.services"; +import { uncertifiedLoadAccountsBalance } from "./wallet-uncertified-accounts.services"; + +class BalanceFetchTracker { + private static instance: BalanceFetchTracker; + private loadedBalances: Set = new Set(); + + public static getInstance(): BalanceFetchTracker { + if (!this.instance) { + this.instance = new BalanceFetchTracker(); + } + return this.instance; + } + + public getNotLoadedIds(ids: CanisterIdString[]): CanisterIdString[] { + const notLoadedIds = ids.filter((id) => !this.loadedBalances.has(id)); + notLoadedIds.forEach((id) => this.loadedBalances.add(id)); + return notLoadedIds; + } + + public reset(): void { + this.loadedBalances.clear(); + } +} + +export const balanceLoader = { + reset() { + BalanceFetchTracker.getInstance().reset(); + }, + async loadAllBalances({ + snsProjects, + ckBTCUniverses, + icrcCanisters, + }: { + snsProjects: SnsFullProject[]; + ckBTCUniverses: Universe[]; + icrcCanisters: IcrcCanistersStoreData; + }) { + await Promise.all([ + this.loadSnsAccountsBalances(snsProjects), + this.loadCkBTCAccountsBalances(ckBTCUniverses), + this.loadIcrcTokenAccounts(icrcCanisters), + ]); + }, + + async loadSnsAccountsBalances(projects: SnsFullProject[]) { + if (projects.length === 0) return; + + const rootCanisterIds = projects.map(({ rootCanisterId }) => + rootCanisterId.toText() + ); + const notLoadedCanisterIds = + BalanceFetchTracker.getInstance().getNotLoadedIds(rootCanisterIds); + + if (notLoadedCanisterIds.length === 0) return; + + await uncertifiedLoadSnsesAccountsBalances({ + rootCanisterIds: notLoadedCanisterIds.map((id) => Principal.fromText(id)), + excludeRootCanisterIds: [], + }); + }, + + async loadCkBTCAccountsBalances(universes: Universe[]) { + const canisterIds = universes.map((universe) => universe.canisterId); + const notLoadedCanisterIds = + BalanceFetchTracker.getInstance().getNotLoadedIds(canisterIds); + + if (notLoadedCanisterIds.length === 0) return; + + // Update balance for each universe + universes.forEach((universe) => { + if (!notLoadedCanisterIds.includes(universe.canisterId)) return; + + const ckBTCCanisters = CKBTC_ADDITIONAL_CANISTERS[universe.canisterId]; + if (nonNullish(ckBTCCanisters.minterCanisterId)) { + updateBalance({ + universeId: Principal.fromText(universe.canisterId), + minterCanisterId: ckBTCCanisters.minterCanisterId, + reload: () => this.loadAccountsBalances([universe.canisterId]), + deferReload: false, + uiIndicators: false, + }); + } + }); + + await this.loadAccountsBalances( + universes.map(({ canisterId }) => canisterId) + ); + }, + + async loadIcrcTokenAccounts(icrcCanisters: IcrcCanistersStoreData) { + const ids = Object.keys(icrcCanisters); + const notLoadedCanisterIds = + BalanceFetchTracker.getInstance().getNotLoadedIds(ids); + + if (notLoadedCanisterIds.length === 0) return; + + await this.loadAccountsBalances(notLoadedCanisterIds); + }, + + async loadAccountsBalances(universeIds: CanisterIdString[]) { + if (isArrayEmpty(universeIds)) return; + + await uncertifiedLoadAccountsBalance({ + universeIds, + excludeUniverseIds: [], + }); + }, +}; diff --git a/frontend/src/routes/(app)/(nns)/portfolio/+page.svelte b/frontend/src/routes/(app)/(nns)/portfolio/+page.svelte index c0dcb5da757..2ef27c77698 100644 --- a/frontend/src/routes/(app)/(nns)/portfolio/+page.svelte +++ b/frontend/src/routes/(app)/(nns)/portfolio/+page.svelte @@ -1,6 +1,28 @@