Skip to content
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
111 changes: 13 additions & 98 deletions app/components/UI/Earn/hooks/useEarnNetworkPolling.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,11 @@ jest.mock('../../../../core/Engine', () => ({
TokenDetectionController: {
detectTokens: jest.fn(),
},
TokensController: {
addTokens: jest.fn(),
Comment thread
cursor[bot] marked this conversation as resolved.
},
},
}));

import { toHex } from '@metamask/controller-utils';
import { CHAIN_ID_TO_AAVE_POOL_CONTRACT } from '@metamask/stake-sdk';
import { renderHookWithProvider } from '../../../../util/test/renderWithProvider';
import useEarnNetworkPolling from './useEarnNetworkPolling';
import { RootState } from '../../../../reducers';
Expand All @@ -39,6 +38,10 @@ import useTokenRatesPolling from '../../../hooks/AssetPolling/useTokenRatesPolli
import useTokenDetectionPolling from '../../../hooks/AssetPolling/useTokenDetectionPolling';
import Engine from '../../../../core/Engine';

const LENDING_CHAIN_IDS = Object.keys(CHAIN_ID_TO_AAVE_POOL_CONTRACT).map(
(chainId) => toHex(chainId),
);

// Mock console.warn to avoid noise in tests
const originalConsoleWarn = console.warn;
beforeAll(() => {
Expand All @@ -61,7 +64,6 @@ describe('useEarnNetworkPolling', () => {
const mockDetectTokens = jest.mocked(
Engine.context.TokenDetectionController.detectTokens,
);
const mockAddTokens = jest.mocked(Engine.context.TokensController.addTokens);

const mockSelectedAccount =
MOCK_ACCOUNTS_CONTROLLER_STATE.internalAccounts.accounts[
Expand Down Expand Up @@ -91,32 +93,6 @@ describe('useEarnNetworkPolling', () => {
PreferencesController: {
useTokenDetection: true,
},
TokensController: {
allDetectedTokens: {
'0x1': {
[mockSelectedAccount.address]: {
'0x123': {
address: '0x123',
symbol: 'TEST',
decimals: 18,
image: 'test-image.png',
name: 'Test Token',
},
},
},
'0x89': {
[mockSelectedAccount.address]: {
'0x456': {
address: '0x456',
symbol: 'TEST2',
decimals: 6,
image: 'test2-image.png',
name: 'Test Token 2',
},
},
},
},
},
},
},
} as unknown as RootState;
Expand All @@ -129,7 +105,6 @@ describe('useEarnNetworkPolling', () => {
throw new Error(`Network client not found for chain ${chainId}`);
});
mockDetectTokens.mockResolvedValue(undefined);
mockAddTokens.mockResolvedValue(undefined);
Comment thread
bergarces marked this conversation as resolved.
});

it('should call all polling hooks when mounted', () => {
Expand All @@ -152,23 +127,24 @@ describe('useEarnNetworkPolling', () => {
});
});

it('should initialize with empty chain IDs and network client IDs', () => {
it('should initialize with lending chain IDs', () => {
renderHookWithProvider(() => useEarnNetworkPolling(), {
state: mockState,
});

// Initially called with empty arrays
const expectedChainIds = expect.arrayContaining(LENDING_CHAIN_IDS);

expect(mockUseTokenBalancesPolling).toHaveBeenCalledWith({
chainIds: [],
chainIds: expectedChainIds,
});
expect(mockUseCurrencyRatePolling).toHaveBeenCalledWith({
chainIds: [],
chainIds: expectedChainIds,
});
expect(mockUseTokenRatesPolling).toHaveBeenCalledWith({
chainIds: [],
chainIds: expectedChainIds,
});
expect(mockUseTokenDetectionPolling).toHaveBeenCalledWith({
chainIds: [],
chainIds: expectedChainIds,
address: mockSelectedAccount.address,
});
});
Expand Down Expand Up @@ -242,54 +218,6 @@ describe('useEarnNetworkPolling', () => {
});
});

it('should call TokensController.addTokens for detected tokens', async () => {
renderHookWithProvider(() => useEarnNetworkPolling(), {
state: mockState,
});

// Wait for async operations to complete
await new Promise((resolve) => setTimeout(resolve, 0));

// Verify tokens are added (order may vary based on LENDING_CHAIN_IDS)
expect(mockAddTokens).toHaveBeenCalledWith(
[
{
address: '0x123',
symbol: 'TEST',
decimals: 18,
image: 'test-image.png',
name: 'Test Token',
isERC721: false,
},
],
'mainnet',
);
});

it('should not call addTokens when no detected tokens', async () => {
const stateWithoutDetectedTokens = {
...mockState,
engine: {
...mockState.engine,
backgroundState: {
...mockState.engine.backgroundState,
TokensController: {
allDetectedTokens: {},
},
},
},
} as unknown as RootState;

renderHookWithProvider(() => useEarnNetworkPolling(), {
state: stateWithoutDetectedTokens,
});

// Wait for async operations to complete
await new Promise((resolve) => setTimeout(resolve, 0));

expect(mockAddTokens).not.toHaveBeenCalled();
});

it('should pass empty chainIds to useTokenDetectionPolling when useTokenDetection is false', () => {
const stateWithoutTokenDetection = {
...mockState,
Expand All @@ -314,19 +242,6 @@ describe('useEarnNetworkPolling', () => {
});
});

it('should handle addTokens errors gracefully', async () => {
mockAddTokens.mockRejectedValue(new Error('Failed to add tokens'));

expect(() => {
renderHookWithProvider(() => useEarnNetworkPolling(), {
state: mockState,
});
}).not.toThrow();

// Wait for async operations to complete
await new Promise((resolve) => setTimeout(resolve, 0));
});

it('should handle detectTokens errors gracefully', async () => {
mockDetectTokens.mockRejectedValue(new Error('Failed to detect tokens'));

Expand Down
72 changes: 7 additions & 65 deletions app/components/UI/Earn/hooks/useEarnNetworkPolling.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Token } from '@metamask/assets-controllers';
import { toHex } from '@metamask/controller-utils';
import { CHAIN_ID_TO_AAVE_POOL_CONTRACT } from '@metamask/stake-sdk';
import { Hex } from '@metamask/utils';
import { useEffect, useState } from 'react';
import { useEffect } from 'react';
import { useSelector } from 'react-redux';
import Engine from '../../../../core/Engine';
import { selectSelectedInternalAccountByScope } from '../../../../selectors/multichainAccounts/accounts';
Expand All @@ -11,7 +10,6 @@ import useCurrencyRatePolling from '../../../hooks/AssetPolling/useCurrencyRateP
import useTokenBalancesPolling from '../../../hooks/AssetPolling/useTokenBalancesPolling';
import useTokenDetectionPolling from '../../../hooks/AssetPolling/useTokenDetectionPolling';
import useTokenRatesPolling from '../../../hooks/AssetPolling/useTokenRatesPolling';
import { RootState } from '../../BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.test';
import { EVM_SCOPE } from '../constants/networks';

/**
Expand Down Expand Up @@ -55,78 +53,22 @@ export const useEarnNetworkPolling = () => {
EVM_SCOPE,
);
const useTokenDetection = useSelector(selectUseTokenDetection);
const tokensState = useSelector(
(state: RootState) => state.engine?.backgroundState?.TokensController,
);
const [lendingChainIds, setLendingChainIds] = useState<Hex[]>([]);

useTokenBalancesPolling({ chainIds: lendingChainIds });
useCurrencyRatePolling({ chainIds: lendingChainIds });
useTokenRatesPolling({ chainIds: lendingChainIds });
useTokenBalancesPolling({ chainIds: LENDING_CHAIN_IDS });
useCurrencyRatePolling({ chainIds: LENDING_CHAIN_IDS });
useTokenRatesPolling({ chainIds: LENDING_CHAIN_IDS });
Comment thread
bergarces marked this conversation as resolved.
useTokenDetectionPolling({
chainIds: useTokenDetection ? lendingChainIds : [],
chainIds: useTokenDetection ? LENDING_CHAIN_IDS : [],
address: selectedAccount?.address as Hex,
});

useEffect(() => {
const validChainIds: Hex[] = [];

LENDING_CHAIN_IDS.forEach((chainId) => {
validChainIds.push(chainId);
});

setLendingChainIds(validChainIds);
}, [setLendingChainIds]);
Copy link
Copy Markdown
Contributor Author

@bergarces bergarces May 15, 2026

Choose a reason for hiding this comment

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

This is really not doing anything, other than causing the first render to call the other hooks with an empty array and then call them again on second render with an exact copy of LENDING_CHAIN_IDS.

I think both the useEffect and the useState are not necessary here.


// Import tokens from all lending chains
useEffect(() => {
const importLendingTokens = async () => {
if (!selectedAccount?.address || !useTokenDetection) return;

const { TokensController } = Engine.context;
const allDetectedTokens = tokensState?.allDetectedTokens || {};

for (const chainId of LENDING_CHAIN_IDS) {
const chainDetectedTokens =
allDetectedTokens[chainId]?.[selectedAccount.address];
if (
chainDetectedTokens &&
Object.keys(chainDetectedTokens).length > 0
) {
const tokensToImport = Object.values(chainDetectedTokens).map(
(token: Token) => ({
address: token.address,
symbol: token.symbol,
decimals: token.decimals,
image: token.image,
name: token.name,
isERC721: false,
}),
);

const networkClientId =
Engine.context.NetworkController.findNetworkClientIdByChainId(
chainId,
);

if (networkClientId && tokensToImport.length > 0) {
await TokensController.addTokens(tokensToImport, networkClientId);
}
}
}
};
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

detectTokens already sends messages to TokensController internally to add the tokens. It does not populate allDetectedTokens.


Engine.context.TokenDetectionController.detectTokens({
chainIds: LENDING_CHAIN_IDS,
selectedAddress: selectedAccount?.address as Hex,
})
.then(importLendingTokens)
.catch(console.error);
}, [
tokensState?.allDetectedTokens,
selectedAccount?.address,
useTokenDetection,
]);
}).catch(console.error);
}, [selectedAccount?.address]);

return null;
};
Expand Down
1 change: 0 additions & 1 deletion app/components/UI/Earn/hooks/useEarnToken.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ const mockState = {
},
},
allIgnoredTokens: {},
allDetectedTokens: {},
} as TokensControllerState,
TokenBalancesController: {
tokenBalances: {
Expand Down
1 change: 0 additions & 1 deletion app/components/UI/Earn/hooks/useEarnTokens.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ const mockState = {
},
},
allIgnoredTokens: {},
allDetectedTokens: {},
} as TokensControllerState,
TokenBalancesController: {
tokenBalances: {
Expand Down
Loading