diff --git a/.changeset/nervous-pumpkins-remain.md b/.changeset/nervous-pumpkins-remain.md
new file mode 100644
index 000000000000..91ee80c43882
--- /dev/null
+++ b/.changeset/nervous-pumpkins-remain.md
@@ -0,0 +1,8 @@
+---
+"@ledgerhq/types-live": patch
+"ledger-live-desktop": patch
+"@ledgerhq/live-common": patch
+"@ledgerhq/live-nft-react": patch
+---
+
+Add useCheckNftAccount Hook
diff --git a/.changeset/short-spoons-notice.md b/.changeset/short-spoons-notice.md
new file mode 100644
index 000000000000..907408392f60
--- /dev/null
+++ b/.changeset/short-spoons-notice.md
@@ -0,0 +1,7 @@
+---
+"ledger-live-desktop": patch
+"@ledgerhq/live-common": patch
+"@ledgerhq/live-nft-react": patch
+---
+
+use Hook CheckNft in Default and handle global sync of NFTs every 12 hours
diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Nfts/screens/Gallery/useNftGalleryModel.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Nfts/screens/Gallery/useNftGalleryModel.tsx
index e22136ad0620..8dfbd271f98d 100644
--- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Nfts/screens/Gallery/useNftGalleryModel.tsx
+++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Nfts/screens/Gallery/useNftGalleryModel.tsx
@@ -3,13 +3,12 @@ import { useDispatch, useSelector } from "react-redux";
import { State } from "~/renderer/reducers";
import { accountSelector } from "~/renderer/reducers/accounts";
import { hiddenNftCollectionsSelector } from "~/renderer/reducers/settings";
-import { useNftGalleryFilter, isThresholdValid } from "@ledgerhq/live-nft-react";
+import { useNftGalleryFilter, isThresholdValid, Chain } from "@ledgerhq/live-nft-react";
import { nftsByCollections } from "@ledgerhq/live-nft";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { openModal } from "~/renderer/actions/modals";
import { useOnScreen } from "LLD/hooks/useOnScreen";
import useFeature from "@ledgerhq/live-common/featureFlags/useFeature";
-import { ChainsEnum } from "LLD/features/Collectibles/types/enum/Chains";
const defaultNumberOfVisibleNfts = 10;
@@ -32,7 +31,7 @@ const useNftGalleryModel = () => {
const { nfts, fetchNextPage, hasNextPage } = useNftGalleryFilter({
nftsOwned: account?.nfts || [],
addresses: account?.freshAddress || "",
- chains: [account?.currency.id ?? ChainsEnum.ETHEREUM],
+ chains: [account?.currency.id ?? Chain.ETHEREUM],
threshold: isThresholdValid(threshold) ? Number(threshold) : 75,
});
diff --git a/apps/ledger-live-desktop/src/renderer/Default.tsx b/apps/ledger-live-desktop/src/renderer/Default.tsx
index 0bed65b6e48a..332883c6189b 100644
--- a/apps/ledger-live-desktop/src/renderer/Default.tsx
+++ b/apps/ledger-live-desktop/src/renderer/Default.tsx
@@ -56,6 +56,7 @@ import { isLocked as isLockedSelector } from "~/renderer/reducers/application";
import { useAutoDismissPostOnboardingEntryPoint } from "@ledgerhq/live-common/postOnboarding/hooks/index";
import { setShareAnalytics, setSharePersonalizedRecommendations } from "./actions/settings";
import useEnv from "@ledgerhq/live-common/hooks/useEnv";
+import { useSyncNFTsWithAccounts } from "./hooks/useSyncNFTsWithAccounts";
const PlatformCatalog = lazy(() => import("~/renderer/screens/platform"));
const Dashboard = lazy(() => import("~/renderer/screens/dashboard"));
@@ -202,12 +203,15 @@ export default function Default() {
useRecoverRestoreOnboarding();
useAutoDismissPostOnboardingEntryPoint();
+ useSyncNFTsWithAccounts();
+
const analyticsFF = useFeature("lldAnalyticsOptInPrompt");
const hasSeenAnalyticsOptInPrompt = useSelector(hasSeenAnalyticsOptInPromptSelector);
const nftReworked = useFeature("lldNftsGalleryNewArch");
const isLocked = useSelector(isLockedSelector);
const dispatch = useDispatch();
const isNftReworkedEnabled = nftReworked?.enabled;
+
useEffect(() => {
if (
!isLocked &&
diff --git a/apps/ledger-live-desktop/src/renderer/components/CustomImage/NFTGallerySelector.tsx b/apps/ledger-live-desktop/src/renderer/components/CustomImage/NFTGallerySelector.tsx
index 74f75add2433..309e5d84c53d 100644
--- a/apps/ledger-live-desktop/src/renderer/components/CustomImage/NFTGallerySelector.tsx
+++ b/apps/ledger-live-desktop/src/renderer/components/CustomImage/NFTGallerySelector.tsx
@@ -10,8 +10,8 @@ import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { useOnScreen } from "~/renderer/screens/nft/useOnScreen";
import { useFeature } from "@ledgerhq/live-common/featureFlags/index";
-import { isThresholdValid, useNftGalleryFilter } from "@ledgerhq/live-nft-react";
import { getEnv } from "@ledgerhq/live-env";
+import { isThresholdValid, useNftGalleryFilter } from "@ledgerhq/live-nft-react";
const ScrollContainer = styled(Flex).attrs({
flexDirection: "column",
diff --git a/apps/ledger-live-desktop/src/renderer/hooks/useSyncNFTsWithAccounts.ts b/apps/ledger-live-desktop/src/renderer/hooks/useSyncNFTsWithAccounts.ts
new file mode 100644
index 000000000000..695c509772cd
--- /dev/null
+++ b/apps/ledger-live-desktop/src/renderer/hooks/useSyncNFTsWithAccounts.ts
@@ -0,0 +1,88 @@
+import { useEffect, useMemo, useState } from "react";
+import { useHideSpamCollection } from "./useHideSpamCollection";
+import { isThresholdValid, supportedChains, useCheckNftAccount } from "@ledgerhq/live-nft-react";
+import { useFeature } from "@ledgerhq/live-common/featureFlags/index";
+import { useSelector } from "react-redux";
+import { accountsSelector, orderedVisibleNftsSelector } from "../reducers/accounts";
+import isEqual from "lodash/isEqual";
+
+/**
+ * Represents the size of groups for batching address fetching.
+ * @constant {number}
+ */
+const GROUP_SIZE = 20;
+
+/**
+ * Represents the timer duration for updating address groups.
+ * 5 hours = 18,000,000 ms.
+ * @constant {number}
+ */
+const TIMER = 5 * 60 * 60 * 1000; // 5 hours = 18000000 ms
+
+/**
+ * A React hook that synchronizes NFT accounts by fetching their data in groups.
+ * It utilizes address batching and manages updates based on a timer.
+ *
+ * @returns {void}
+ *
+ * @example
+ * import { useSyncNFTsWithAccounts } from './path/to/hook';
+ *
+ * const MyComponent = () => {
+ * useSyncNFTsWithAccounts();
+ * return
Syncing NFT Accounts...
;
+ * };
+ */
+
+export function useSyncNFTsWithAccounts() {
+ const nftsFromSimplehashFeature = useFeature("nftsFromSimplehash");
+ const threshold = isThresholdValid(Number(nftsFromSimplehashFeature?.params?.threshold))
+ ? Number(nftsFromSimplehashFeature?.params?.threshold)
+ : 75;
+
+ const { enabled, hideSpamCollection } = useHideSpamCollection();
+
+ const accounts = useSelector(accountsSelector);
+ const nftsOwned = useSelector(orderedVisibleNftsSelector, isEqual);
+
+ const addressGroups = useMemo(() => {
+ const uniqueAddresses = [
+ ...new Set(
+ accounts.map(account => account.freshAddress).filter(addr => addr.startsWith("0x")),
+ ),
+ ];
+
+ return uniqueAddresses.reduce((acc, _, i, arr) => {
+ if (i % GROUP_SIZE === 0) {
+ acc.push(arr.slice(i, i + GROUP_SIZE));
+ }
+ return acc;
+ }, []);
+ }, [accounts]);
+
+ const [groupToFetch, setGroupToFetch] = useState(addressGroups[0]);
+ const [, setCurrentIndex] = useState(0);
+
+ useEffect(() => {
+ if (!enabled) return;
+ const interval = setInterval(() => {
+ setCurrentIndex(prevIndex => {
+ const nextIndex = (prevIndex + 1) % addressGroups.length;
+ setGroupToFetch(addressGroups[nextIndex]);
+
+ return nextIndex;
+ });
+ }, TIMER);
+
+ return () => clearInterval(interval);
+ }, [addressGroups, enabled]);
+
+ useCheckNftAccount({
+ addresses: groupToFetch.join(","),
+ nftsOwned,
+ chains: supportedChains,
+ threshold,
+ action: hideSpamCollection,
+ enabled,
+ });
+}
diff --git a/apps/ledger-live-desktop/src/renderer/screens/nft/Gallery/Gallery.tsx b/apps/ledger-live-desktop/src/renderer/screens/nft/Gallery/Gallery.tsx
index 2659c6c90afc..6c2fd6ba14b1 100644
--- a/apps/ledger-live-desktop/src/renderer/screens/nft/Gallery/Gallery.tsx
+++ b/apps/ledger-live-desktop/src/renderer/screens/nft/Gallery/Gallery.tsx
@@ -20,7 +20,7 @@ import { State } from "~/renderer/reducers";
import { ProtoNFT } from "@ledgerhq/types-live";
import theme from "@ledgerhq/react-ui/styles/theme";
import { useOnScreen } from "../useOnScreen";
-import { isThresholdValid, useNftGalleryFilter } from "@ledgerhq/live-nft-react";
+import { Chain, isThresholdValid, useNftGalleryFilter } from "@ledgerhq/live-nft-react";
import { useFeature } from "@ledgerhq/live-common/featureFlags/index";
const SpinnerContainer = styled.div`
@@ -69,7 +69,7 @@ const Gallery = () => {
const { nfts, fetchNextPage, hasNextPage } = useNftGalleryFilter({
nftsOwned: account?.nfts || [],
addresses: account?.freshAddress || "",
- chains: [account?.currency.id ?? "ethereum"],
+ chains: [account?.currency.id ?? Chain.ETHEREUM],
threshold: isThresholdValid(thresold) ? Number(thresold) : 75,
});
diff --git a/libs/ledger-live-common/src/market/utils/timers.ts b/libs/ledger-live-common/src/market/utils/timers.ts
index 2e9bac6e03b4..1a01e24cbb1f 100644
--- a/libs/ledger-live-common/src/market/utils/timers.ts
+++ b/libs/ledger-live-common/src/market/utils/timers.ts
@@ -3,3 +3,4 @@ export const REFETCH_TIME_ONE_MINUTE = 60 * 1000;
export const BASIC_REFETCH = 3; // nb minutes
export const ONE_DAY = 24 * 60 * 60 * 1000;
+export const HALF_DAY = ONE_DAY / 2;
diff --git a/libs/live-nft-react/src/hooks/types.ts b/libs/live-nft-react/src/hooks/types.ts
index 8a33820aa55c..051f88385404 100644
--- a/libs/live-nft-react/src/hooks/types.ts
+++ b/libs/live-nft-react/src/hooks/types.ts
@@ -19,6 +19,7 @@ export type HookProps = {
chains: string[];
threshold: number;
action?: (collection: string) => void;
+ enabled?: boolean;
};
export type PartialProtoNFT = Partial;
diff --git a/libs/live-nft-react/src/hooks/useCheckNftAccount.ts b/libs/live-nft-react/src/hooks/useCheckNftAccount.ts
index 4eaad5874cbd..6d67b3f56ef4 100644
--- a/libs/live-nft-react/src/hooks/useCheckNftAccount.ts
+++ b/libs/live-nft-react/src/hooks/useCheckNftAccount.ts
@@ -9,19 +9,32 @@ import { nftsByCollections } from "@ledgerhq/live-nft/index";
import { hashProtoNFT } from "./helpers";
/**
- * useCheckNftAccount() will apply a spam filtering on top of existing NFT data.
- * - addresses: a list of wallet addresses separated by a ","
- * - nftOwned: the array of all nfts as found by all user's account on Ledger Live
- * - chains: a list of selected network to search for NFTs
- * - action: custom action to handle collections
- * NB: for performance, make sure that addresses, nftOwned and chains are memoized
+ * A React hook that checks NFT accounts against specified criteria and provides filtering functionality for managing NFT collections.
+ *
+ * @param {Object} params - The parameters for the hook.
+ * @param {string} params.addresses - A comma-separated string of NFT addresses to check.
+ * @param {Array} params.nftsOwned - An array of owned NFTs.
+ * @param {Array} params.chains - An array representing the blockchain chains.
+ * @param {number} params.threshold - A numeric threshold for filtering NFTs.
+ * @param {Function} params.action - A callback function to execute when spam is detected.
+ * @param {boolean} [params.enabled=false] - A flag to enable or disable the hook's functionality.
+ *
+ * @returns {Object} The result of the hook.
+ * @returns {Array} returns.nfts - An array of filtered NFTs.
+ * @returns {Object} returns.queryResult - The result of the infinite query, containing pagination and loading states.
+ *
*/
+
+export const ONE_DAY = 24 * 60 * 60 * 1000;
+export const HALF_DAY = ONE_DAY / 2;
+
export function useCheckNftAccount({
addresses,
nftsOwned,
chains,
threshold,
action,
+ enabled,
}: HookProps): NftsFilterResult {
// for performance, we hashmap the list of nfts by hash.
const nftsWithProperties = useMemo(
@@ -36,7 +49,9 @@ export function useCheckNftAccount({
fetchNftsFromSimpleHash({ addresses, chains, cursor: pageParam, threshold }),
initialPageParam: undefined,
getNextPageParam: lastPage => lastPage.next_cursor,
- enabled: addresses.length > 0,
+ enabled: enabled && addresses.length > 0,
+ refetchInterval: HALF_DAY,
+ staleTime: HALF_DAY,
});
useEffect(() => {
diff --git a/libs/live-nft-react/src/index.ts b/libs/live-nft-react/src/index.ts
index bbe9948fc0b9..09f4ee858db1 100644
--- a/libs/live-nft-react/src/index.ts
+++ b/libs/live-nft-react/src/index.ts
@@ -10,3 +10,4 @@ export * from "./hooks/helpers/ordinals";
export * from "./hooks/useCheckNftAccount";
export * from "./hooks/helpers/index";
export * from "./hooks/types";
+export * from "./supported";
diff --git a/libs/live-nft-react/src/supported.ts b/libs/live-nft-react/src/supported.ts
new file mode 100644
index 000000000000..309d048b39dd
--- /dev/null
+++ b/libs/live-nft-react/src/supported.ts
@@ -0,0 +1,6 @@
+export enum Chain {
+ ETHEREUM = "ethereum",
+ POLYGON = "polygon",
+}
+
+export const supportedChains = [Chain.ETHEREUM, Chain.POLYGON];