Skip to content

Commit

Permalink
feat: ✨ Add spamFilteringTx hook in global
Browse files Browse the repository at this point in the history
feat: ✨ Add spamFilteringTx hook in global

review: Enums
  • Loading branch information
mcayuelas-ledger committed Oct 29, 2024
1 parent 19109d6 commit 4febbaa
Show file tree
Hide file tree
Showing 12 changed files with 143 additions and 13 deletions.
8 changes: 8 additions & 0 deletions .changeset/nervous-pumpkins-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@ledgerhq/types-live": patch
"ledger-live-desktop": patch
"@ledgerhq/live-common": patch
"@ledgerhq/live-nft-react": patch
---

Add useCheckNftAccount Hook
7 changes: 7 additions & 0 deletions .changeset/short-spoons-notice.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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,
});

Expand Down
4 changes: 4 additions & 0 deletions apps/ledger-live-desktop/src/renderer/Default.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand Down Expand Up @@ -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 &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <div>Syncing NFT Accounts...</div>;
* };
*/

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<string[][]>((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,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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,
});

Expand Down
1 change: 1 addition & 0 deletions libs/ledger-live-common/src/market/utils/timers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
1 change: 1 addition & 0 deletions libs/live-nft-react/src/hooks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type HookProps = {
chains: string[];
threshold: number;
action?: (collection: string) => void;
enabled?: boolean;
};

export type PartialProtoNFT = Partial<ProtoNFT>;
Expand Down
29 changes: 22 additions & 7 deletions libs/live-nft-react/src/hooks/useCheckNftAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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(() => {
Expand Down
1 change: 1 addition & 0 deletions libs/live-nft-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
6 changes: 6 additions & 0 deletions libs/live-nft-react/src/supported.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum Chain {
ETHEREUM = "ethereum",
POLYGON = "polygon",
}

export const supportedChains = [Chain.ETHEREUM, Chain.POLYGON];

0 comments on commit 4febbaa

Please sign in to comment.