Skip to content
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
fcfcfba
feat: Implement CacheManager for caching functionality
AugmentedMode Sep 7, 2025
1abf610
refactor: Remove unused token scan cache methods from PhishingController
AugmentedMode Sep 7, 2025
635a383
refactor: Remove unused token scan cache methods from PhishingController
AugmentedMode Sep 7, 2025
be9d2fd
refactor: Remove unused token scan cache methods from PhishingController
AugmentedMode Sep 7, 2025
327f178
refactor: Remove unused token scan cache methods from PhishingController
AugmentedMode Sep 7, 2025
4a1d5bd
refactor: Remove unused scanToken action and related methods from Phi…
AugmentedMode Sep 7, 2025
d8fed33
refactor: Update TokenScanResult structure in PhishingController to u…
AugmentedMode Sep 7, 2025
2c82606
feat: Add bulk token screening endpoint and refactor chain ID handlin…
AugmentedMode Sep 10, 2025
81b5ad8
refactor: Simplify BulkTokenScanResponse structure and improve error …
AugmentedMode Sep 10, 2025
2b26ffb
fix: unused error var
AugmentedMode Sep 10, 2025
3a2b484
refactor: Update TokenScanResult structure in PhishingController to u…
AugmentedMode Sep 10, 2025
899065f
refactor: Update cache configuration defaults in PhishingController t…
AugmentedMode Sep 10, 2025
7564a52
refactor: Move TokenScanResult and related types to types.ts
AugmentedMode Sep 10, 2025
7900e2e
fix: prettier in types
AugmentedMode Sep 10, 2025
36028ce
refactor: Update chain ID mapping in PhishingController to use hex fo…
AugmentedMode Sep 10, 2025
6827d6e
refactor: Introduce BulkTokenScanRequest type and update bulkScanToke…
AugmentedMode Sep 10, 2025
d057228
refactor: Increase token limit to 100 and enhance error handling in P…
AugmentedMode Sep 10, 2025
d4582f1
chore: update changelog to reflec bulk token scanning feat
AugmentedMode Sep 10, 2025
aeabb87
test: phishing controller
AugmentedMode Sep 10, 2025
5fe4463
test: add error handling test for bulkScanTokens in PhishingController
AugmentedMode Sep 14, 2025
0ed7df7
feat: implement fire-and-forget bulk token screening in TransactionCo…
AugmentedMode Sep 14, 2025
029816d
fix: merge conflicts
AugmentedMode Sep 14, 2025
a4f70a6
Merge branch 'feat/add-bulk-token-screening' of https://github.com/Me…
AugmentedMode Sep 14, 2025
87454ad
refactor: rename and enhance bulk token screening method in Transacti…
AugmentedMode Sep 14, 2025
d9d8f1f
chore: add debug logging for token screening in PhishingController an…
AugmentedMode Sep 14, 2025
3880695
fix: logging
AugmentedMode Sep 15, 2025
3157ee4
chore: cleanup comments
AugmentedMode Sep 15, 2025
f734c70
chore: cleanup unused var
AugmentedMode Sep 15, 2025
555b930
refactor: normalize chain ID handling and improve error logging in Ph…
AugmentedMode Sep 16, 2025
5bf6ff7
refactor: rename TOKEN_BULK_SCREENING_ENDPOINT to TOKEN_BULK_SCANNING…
AugmentedMode Sep 16, 2025
f4c2a39
chore: update CHANGELOG to reflect addition of bulk token scanning fu…
AugmentedMode Sep 16, 2025
c40a42c
feat: add bulk token scanning functionality to PhishingController
AugmentedMode Sep 16, 2025
0464330
chore: cleanup
AugmentedMode Sep 16, 2025
ea4f9e0
fix: tests by adding bulk scan tokens as an allowedAction in tests
AugmentedMode Sep 16, 2025
ad8b29a
chore: update changelog
AugmentedMode Sep 16, 2025
3ab1cc3
fix: typo in changelog
AugmentedMode Sep 16, 2025
843a072
refactor: simplify error handling in bulk token scanning by removing …
AugmentedMode Sep 16, 2025
bf15575
fix: changelog
AugmentedMode Sep 16, 2025
234b63a
chore: remove unused has function in cacheManager
AugmentedMode Sep 18, 2025
bc4a580
refactor: bulk token scan function into helper functions
AugmentedMode Sep 19, 2025
a0fa426
fix: linter
AugmentedMode Sep 19, 2025
a9aef02
refactor: phishing controller to subscribe to tx controller asset cha…
AugmentedMode Sep 19, 2025
df4386a
fix: prettier
AugmentedMode Sep 19, 2025
f25b25e
chore: remove bulk token scanning tests and update changelog
AugmentedMode Sep 19, 2025
f0efee0
refactor: implement start and stop methods for transaction controller…
AugmentedMode Sep 21, 2025
9373838
refactor: streamline transaction state change handling
AugmentedMode Sep 21, 2025
e02f3ac
refactor: remove redundant logging
AugmentedMode Sep 21, 2025
ab222aa
chore: update SECURITY_ALERTS_BASE_URL to use the production endpoint
AugmentedMode Sep 21, 2025
39fae01
test: enhance PhishingController tests
AugmentedMode Sep 21, 2025
d78e289
chore: update changelog
AugmentedMode Sep 22, 2025
9e3dce8
fix: tests
AugmentedMode Sep 22, 2025
db68239
refactor: streamline token scanning logic and improve cache handling …
AugmentedMode Sep 22, 2025
af392b3
fix: remove unused vars
AugmentedMode Sep 22, 2025
7ff17dd
test: bulk scan token functions
AugmentedMode Sep 22, 2025
b2cd05e
pull in latest
AugmentedMode Sep 22, 2025
82fe3bb
refactor: optimize transaction state change handling in PhishingContr…
AugmentedMode Sep 22, 2025
b531cda
fix: merge conflicts
AugmentedMode Sep 22, 2025
ba6a23b
fix: transaction controller verison
AugmentedMode Sep 22, 2025
3e98c75
chore: cleanup
AugmentedMode Sep 22, 2025
dd43cc0
chore: cleanup
AugmentedMode Sep 22, 2025
d9b5a8e
chore: cleamup
AugmentedMode Sep 22, 2025
60e9c63
refactor: streamline token address extraction
AugmentedMode Sep 23, 2025
1746183
chore: update SECURITY_ALERTS_BASE_URL to production endpoint and sim…
AugmentedMode Sep 24, 2025
e38edb4
test: add integration tests for transaction state change handling in …
AugmentedMode Sep 24, 2025
3d01c6f
fix: update transaction patch check to require exact path length of 2
AugmentedMode Sep 24, 2025
a7ce018
Merge branch 'main' into feat/add-token-screening-tx-controller
AugmentedMode Sep 24, 2025
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
6 changes: 6 additions & 0 deletions packages/phishing-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add bulk token scanning functionality to detect malicious tokens ([#6483](https://github.com/MetaMask/core/pull/6483))
- Add `bulkScanTokens` method to scan multiple tokens for malicious activity
- Add `BulkTokenScanRequest` and `BulkTokenScanResponse` types
- Add `tokenScanCache` to `PhishingControllerState`
- Add proper action registration for `bulkScanTokens` method as `PhishingControllerBulkScanTokensAction`
- Support for multiple chains including Ethereum, Polygon, BSC, Arbitrum, Avalanche, Base, Optimism, ect...
- Add two new controller state metadata properties: `includeInStateLogs` and `usedInUi` ([#6587](https://github.com/MetaMask/core/pull/6587))

### Changed
Expand Down
219 changes: 219 additions & 0 deletions packages/phishing-controller/src/CacheManager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import sinon from 'sinon';

import { CacheManager } from './CacheManager';
import * as utils from './utils';

describe('CacheManager', () => {
let clock: sinon.SinonFakeTimers;
let updateStateSpy: sinon.SinonSpy;
let cache: CacheManager<{ value: string }>;

beforeEach(() => {
clock = sinon.useFakeTimers();
sinon
.stub(utils, 'fetchTimeNow')
.callsFake(() => Math.floor(Date.now() / 1000));
updateStateSpy = sinon.spy();
cache = new CacheManager<{ value: string }>({
cacheTTL: 300, // 5 minutes
maxCacheSize: 3,
updateState: updateStateSpy,
});
});

afterEach(() => {
sinon.restore();
});

describe('constructor', () => {
it('should initialize with empty cache when no initialCache provided', () => {
const emptyCache = new CacheManager<{ value: string }>({
// eslint-disable-next-line no-empty-function
updateState: () => {},
});
expect(emptyCache.get('test-key')).toBeUndefined();
});

it('should initialize with provided initialCache data', () => {
const now = Math.floor(Date.now() / 1000);
const initialCache = {
'test-key': {
data: { value: 'test-value' },
timestamp: now,
},
};

const cacheWithInitialData = new CacheManager<{ value: string }>({
initialCache,
// eslint-disable-next-line no-empty-function
updateState: () => {},
});

expect(cacheWithInitialData.get('test-key')).toStrictEqual({
value: 'test-value',
});
});
});

describe('get', () => {
it('should return undefined for non-existent keys', () => {
expect(cache.get('non-existent')).toBeUndefined();
});

it('should return data for existing keys', () => {
cache.set('key1', { value: 'value1' });
expect(cache.get('key1')).toStrictEqual({ value: 'value1' });
});

it('should return undefined for expired entries', () => {
cache.set('key1', { value: 'value1' });

// Fast forward time past TTL
clock.tick(301 * 1000);

expect(cache.get('key1')).toBeUndefined();
});
});

describe('set', () => {
it('should add new entries', () => {
cache.set('key1', { value: 'value1' });
expect(cache.get('key1')).toStrictEqual({ value: 'value1' });
});

it('should update existing entries', () => {
cache.set('key1', { value: 'value1' });
cache.set('key1', { value: 'updated-value' });
expect(cache.get('key1')).toStrictEqual({ value: 'updated-value' });
});

it('should call updateState when adding entries', () => {
cache.set('key1', { value: 'value1' });
expect(updateStateSpy.calledOnce).toBe(true);
});

it('should evict oldest entries when cache exceeds max size', () => {
cache.set('key1', { value: 'value1' });
cache.set('key2', { value: 'value2' });
cache.set('key3', { value: 'value3' });
cache.set('key4', { value: 'value4' }); // This should evict key1

expect(cache.get('key1')).toBeUndefined();
expect(cache.get('key2')).toStrictEqual({ value: 'value2' });
expect(cache.get('key3')).toStrictEqual({ value: 'value3' });
expect(cache.get('key4')).toStrictEqual({ value: 'value4' });
});
});

describe('has', () => {
it('should return false for non-existent keys', () => {
expect(cache.has('non-existent')).toBe(false);
});

it('should return true for existing keys', () => {
cache.set('key1', { value: 'value1' });
expect(cache.has('key1')).toBe(true);
});

it('should return true for expired keys', () => {
cache.set('key1', { value: 'value1' });
clock.tick(301 * 1000);
expect(cache.has('key1')).toBe(true);
});
});

describe('delete', () => {
it('should remove entries', () => {
cache.set('key1', { value: 'value1' });
expect(cache.delete('key1')).toBe(true);
expect(cache.get('key1')).toBeUndefined();
});

it('should return false when deleting non-existent keys', () => {
expect(cache.delete('non-existent')).toBe(false);
});

it('should call updateState when deleting entries', () => {
cache.set('key1', { value: 'value1' });
updateStateSpy.resetHistory();
cache.delete('key1');
expect(updateStateSpy.calledOnce).toBe(true);
});
});

describe('clear', () => {
it('should remove all entries', () => {
cache.set('key1', { value: 'value1' });
cache.set('key2', { value: 'value2' });
cache.clear();
expect(cache.get('key1')).toBeUndefined();
expect(cache.get('key2')).toBeUndefined();
});

it('should call updateState', () => {
cache.set('key1', { value: 'value1' });
updateStateSpy.resetHistory();
cache.clear();
expect(updateStateSpy.calledOnce).toBe(true);
});
});

describe('setTTL', () => {
it('should update the TTL', () => {
cache.setTTL(600);
expect(cache.getTTL()).toBe(600);
});
});

describe('setMaxSize', () => {
it('should update the max size', () => {
cache.setMaxSize(5);
expect(cache.getMaxSize()).toBe(5);
});

it('should evict entries if new size is smaller than current cache size', () => {
cache.set('key1', { value: 'value1' });
cache.set('key2', { value: 'value2' });
cache.set('key3', { value: 'value3' });
cache.setMaxSize(2); // This should evict key1

expect(cache.get('key1')).toBeUndefined();
expect(cache.get('key2')).toStrictEqual({ value: 'value2' });
expect(cache.get('key3')).toStrictEqual({ value: 'value3' });
});
});

describe('getSize', () => {
it('should return the current cache size', () => {
expect(cache.getSize()).toBe(0);
cache.set('key1', { value: 'value1' });
expect(cache.getSize()).toBe(1);
cache.set('key2', { value: 'value2' });
expect(cache.getSize()).toBe(2);
cache.delete('key1');
expect(cache.getSize()).toBe(1);
});
});

describe('keys', () => {
it('should return all cache keys', () => {
cache.set('key1', { value: 'value1' });
cache.set('key2', { value: 'value2' });
expect(cache.keys()).toStrictEqual(['key1', 'key2']);
});
});

describe('getAllEntries', () => {
it('should return all cache entries', () => {
const now = Math.floor(Date.now() / 1000);
cache.set('key1', { value: 'value1' });
cache.set('key2', { value: 'value2' });
const entries = cache.getAllEntries();
expect(Object.keys(entries)).toStrictEqual(['key1', 'key2']);
expect(entries.key1.data).toStrictEqual({ value: 'value1' });
expect(entries.key2.data).toStrictEqual({ value: 'value2' });
expect(entries.key1.timestamp).toBeGreaterThanOrEqual(now);
expect(entries.key2.timestamp).toBeGreaterThanOrEqual(now);
});
});
});
Loading
Loading