Skip to content
Open
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
7 changes: 7 additions & 0 deletions .changeset/slow-knives-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"ledger-live-desktop": minor
"live-mobile": minor
"@ledgerhq/live-common": minor
---

Add feature flag for address poisoning operations filter
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { isAddressPoisoningOperation } from "@ledgerhq/coin-framework/operation"
import { Operation, AccountLike } from "@ledgerhq/types-live";
import { TFunction } from "i18next";
import { useFilterTokenOperationsZeroAmount } from "~/renderer/actions/settings";
import { useAddressPoisoningOperationsFamilies } from "@ledgerhq/live-common/hooks/useAddressPoisoningOperationsFamilies";

export interface PortfolioViewModelResult {
readonly totalAccounts: number;
Expand All @@ -34,16 +35,22 @@ export const usePortfolioViewModel = (): PortfolioViewModelResult => {
} = useWalletFeaturesConfig("desktop");
const { t } = useTranslation();
const [shouldFilterTokenOpsZeroAmount] = useFilterTokenOperationsZeroAmount();
const addressPoisoningFamilies = useAddressPoisoningOperationsFamilies({
shouldFilter: shouldFilterTokenOpsZeroAmount,
});

const filterOperations = useCallback(
(operation: Operation, account: AccountLike) => {
// Remove operations linked to address poisoning
const removeZeroAmountTokenOp =
shouldFilterTokenOpsZeroAmount && isAddressPoisoningOperation(operation, account);
shouldFilterTokenOpsZeroAmount &&
isAddressPoisoningOperation(operation, account, {
families: addressPoisoningFamilies ? addressPoisoningFamilies : undefined,
});

return !removeZeroAmountTokenOp;
},
[shouldFilterTokenOpsZeroAmount],
[shouldFilterTokenOpsZeroAmount, addressPoisoningFamilies],
);

const totalAccounts = accounts.length;
Expand Down
13 changes: 11 additions & 2 deletions apps/ledger-live-desktop/src/renderer/screens/account/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { AccountLike, Account, Operation } from "@ledgerhq/types-live";
import { State } from "~/renderer/reducers";
import { getLLDCoinFamily } from "~/renderer/families";
import NftEntryPoint from "LLD/features/NftEntryPoint";
import { useAddressPoisoningOperationsFamilies } from "@ledgerhq/live-common/hooks/useAddressPoisoningOperationsFamilies";

type Params = {
id?: string;
Expand Down Expand Up @@ -91,16 +92,24 @@ const AccountPage = ({
const PendingTransferProposals = specific?.PendingTransferProposals;
const bgColor = useTheme().colors.background.card;
const [shouldFilterTokenOpsZeroAmount] = useFilterTokenOperationsZeroAmount();
const addressPoisoningFamilies = useAddressPoisoningOperationsFamilies({
shouldFilter: shouldFilterTokenOpsZeroAmount,
});

const filterOperations = useCallback(
(operation: Operation, account: AccountLike) => {
// Remove operations linked to address poisoning
const removeZeroAmountTokenOp =
shouldFilterTokenOpsZeroAmount && isAddressPoisoningOperation(operation, account);
shouldFilterTokenOpsZeroAmount &&
isAddressPoisoningOperation(
operation,
account,
addressPoisoningFamilies ? { families: addressPoisoningFamilies } : undefined,
);

return !removeZeroAmountTokenOp;
},
[shouldFilterTokenOpsZeroAmount],
[shouldFilterTokenOpsZeroAmount, addressPoisoningFamilies],
);

const currency = mainAccount?.currency;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import SwapWebViewEmbedded from "./components/SwapWebViewEmbedded";
import BannerSection from "./components/BannerSection";
import { MarketBanner as MarketBannerFeature } from "@features/market-banner";
import Portfolio from "LLD/features/Portfolio";
import { useAddressPoisoningOperationsFamilies } from "@ledgerhq/live-common/hooks/useAddressPoisoningOperationsFamilies";

// This forces only one visible top banner at a time
export const TopBannerContainer = styled.div`
Expand All @@ -53,16 +54,24 @@ export default function DashboardPage() {
[accounts],
);
const [shouldFilterTokenOpsZeroAmount] = useFilterTokenOperationsZeroAmount();
const addressPoisoningFamilies = useAddressPoisoningOperationsFamilies({
shouldFilter: shouldFilterTokenOpsZeroAmount,
});

const filterOperations = useCallback(
(operation: Operation, account: AccountLike) => {
// Remove operations linked to address poisoning
const removeZeroAmountTokenOp =
shouldFilterTokenOpsZeroAmount && isAddressPoisoningOperation(operation, account);
shouldFilterTokenOpsZeroAmount &&
isAddressPoisoningOperation(
operation,
account,
addressPoisoningFamilies ? { families: addressPoisoningFamilies } : undefined,
);

return !removeZeroAmountTokenOp;
},
[shouldFilterTokenOpsZeroAmount],
[shouldFilterTokenOpsZeroAmount, addressPoisoningFamilies],
);

const { isFeatureFlagsAnalyticsPrefDisplayed, analyticsOptInPromptProps } =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
import { groupAccountsOperationsByDay } from "@ledgerhq/coin-framework/lib/account/groupOperations";
import { isAddressPoisoningOperation } from "@ledgerhq/coin-framework/lib/operation";
import { Operation, AccountLike } from "@ledgerhq/types-live";
import { AccountLike, Operation } from "@ledgerhq/types-live";
import { useCallback } from "react";
import { useSelector } from "~/context/hooks";
import { filterTokenOperationsZeroAmountEnabledSelector } from "~/reducers/settings";
import { useAddressPoisoningOperationsFamilies } from "@ledgerhq/live-common/hooks/useAddressPoisoningOperationsFamilies";

export function useOperationsV1(accounts: AccountLike[], opCount: number) {
const shouldFilterTokenOpsZeroAmount = useSelector(
filterTokenOperationsZeroAmountEnabledSelector,
);

const addressPoisoningFamilies = useAddressPoisoningOperationsFamilies({
shouldFilter: shouldFilterTokenOpsZeroAmount,
});

const filterOperation = useCallback(
(operation: Operation, account: AccountLike) => {
// Remove operations linked to address poisoning
const removeZeroAmountTokenOp =
shouldFilterTokenOpsZeroAmount && isAddressPoisoningOperation(operation, account);
shouldFilterTokenOpsZeroAmount &&
isAddressPoisoningOperation(
operation,
account,
addressPoisoningFamilies ? { families: addressPoisoningFamilies } : undefined,
);

return !removeZeroAmountTokenOp;
},
[shouldFilterTokenOpsZeroAmount],
[shouldFilterTokenOpsZeroAmount, addressPoisoningFamilies],
);

const { sections, completed } = groupAccountsOperationsByDay(accounts, {
Expand Down
30 changes: 30 additions & 0 deletions libs/coin-framework/src/operation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,36 @@ describe("Operation.ts", () => {

expect(isAddressPoisoningOperation(operation, account)).toBe(false);
});

it("shouldn't filter operation if it's a Account at 0 value", () => {
const account = genAccount("myAccount", { currency: ethereum });
const tokenAccount = genTokenAccount(0, account, lobster);
const operation = {
...genOperation(account, tokenAccount, account.operations, new Prando("")),
value: new BigNumber(0),
};

expect(isAddressPoisoningOperation(operation, account)).toBe(false);
});

it("should use options.families array when provided (feature-flag path)", () => {
const account = genAccount("myAccount", { currency: ethereum });
const tokenAccount = genTokenAccount(0, account, usdc);
const operation = {
...genOperation(account, tokenAccount, account.operations, new Prando("")),
value: new BigNumber(0),
};
const families = ["evm"];

expect(isAddressPoisoningOperation(operation, tokenAccount, { families: families })).toBe(
true,
);
expect(
isAddressPoisoningOperation(operation, tokenAccount, {
families: ["algorand"],
}),
).toBe(false);
});
});

describe("isOldestPendingOperation", () => {
Expand Down
25 changes: 18 additions & 7 deletions libs/coin-framework/src/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,18 +235,29 @@ export const isConfirmedOperation = (
? account.blockHeight - operation.blockHeight + 1 >= confirmationsNb
: false;

type AddressPoisoningFilterOptions = {
families?: string[] | null;
};

export const isAddressPoisoningOperation = (
operation: Operation,
account: AccountLike,
options?: AddressPoisoningFilterOptions,
): boolean => {
const impactedFamilies = getEnv("ADDRESS_POISONING_FAMILIES").split(",");
const isTokenAccount = account.type === "TokenAccount";
if (!operation.value.isZero() || account.type !== "TokenAccount") return false;

return (
isTokenAccount &&
impactedFamilies.includes(account.token.parentCurrency.family) &&
operation.value.isZero()
);
const family = account.token.parentCurrency.family;

if (options?.families) {
return options.families.includes(family);
}

// Fallback to environment variable if no families are provided to be retro-compatible
const impactedFamilies = getEnv("ADDRESS_POISONING_FAMILIES")
.split(",")
.map(s => s.trim());

return impactedFamilies.includes(family);
};

/**
Expand Down
1 change: 1 addition & 0 deletions libs/ledger-live-common/.unimportedrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@
"src/hoc/withRemountableWrapper.tsx",
"src/hooks/recoverFeatureFlag.ts",
"src/hooks/useAccountsWithFundsListener.ts",
"src/hooks/useAddressPoisoningOperationsFamilies.ts",
"src/hooks/useBroadcast.ts",
"src/hooks/useDBRaw.ts",
"src/hooks/useDebounce.ts",
Expand Down
17 changes: 17 additions & 0 deletions libs/ledger-live-common/src/featureFlags/defaultFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,23 @@ export const DEFAULT_FEATURES: Features = {
quickActionCtas: true,
},
},
addressPoisoningOperationsFilter: {
...DEFAULT_FEATURE,
enabled: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it expected to be activated by default ?

params: {
families: [
"evm",
"tron",
"solana",
"xrp",
"stellar",
"hedera",
"algorand",
"cardano",
"cosmos",
],
},
},
};

// Firebase SDK treat JSON values as strings
Expand Down
Loading
Loading