Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
81948a4
feat(perps): add disk-backed cold-start cache for instant data display
abretonc7s Mar 24, 2026
6fe0beb
chore(perps): add cold-start cache benchmark instrumentation and meth…
abretonc7s Mar 25, 2026
097ed71
chore(perps): realistic benchmark recipe + Android/iOS results
abretonc7s Mar 25, 2026
9594b2c
feat(perps): add skipTTL cache getters, price stripping, and benchmar…
abretonc7s Mar 25, 2026
38415ad
chore(perps): remove temporary benchmark instrumentation
abretonc7s Mar 25, 2026
d73f553
Merge remote-tracking branch 'origin/main' into feat/perps/simplify-c…
abretonc7s Mar 25, 2026
7700407
test(perps): add unit tests for disk-backed cold-start cache
abretonc7s Mar 25, 2026
c87935b
docs(perps): remove proposal file, add caching architecture doc
abretonc7s Mar 25, 2026
8a02c0d
fix(perps): disk cache DI consistency, dedup providerNetworkKey, remo…
abretonc7s Mar 25, 2026
f5b0d93
fix(perps): move throttle timestamp after validation guards in disk p…
abretonc7s Mar 25, 2026
930efa1
fix(perps): guard disk cache setItem with optional chaining for test …
abretonc7s Mar 25, 2026
8f91501
Merge remote-tracking branch 'origin/main' into feat/perps/simplify-c…
abretonc7s Mar 25, 2026
e14a05e
fix(perps): reset disk cache throttle timestamps on context switch
abretonc7s Mar 25, 2026
b9b1ece
test(perps): add resetDiskCacheThrottles to stream manager mock
abretonc7s Mar 25, 2026
4c865cb
fix(perps): synchronous disk cache hydration to eliminate cold-start …
abretonc7s Mar 26, 2026
f4df5c0
chore(perps): remove cold-start cache recipe from PR (shared as PR co…
abretonc7s Mar 26, 2026
37e45cf
chore(perps): remove benchmark methodology doc (not needed in PR)
abretonc7s Mar 26, 2026
799cc46
fix(perps): remove dead async hydrateCacheFromDisk and redundant sync…
abretonc7s Mar 26, 2026
9485084
Merge remote-tracking branch 'origin/main' into feat/perps/simplify-c…
abretonc7s Mar 26, 2026
bd329aa
fix(perps): only persist user data to disk when all channels have del…
abretonc7s Mar 26, 2026
2b88ff4
Merge remote-tracking branch 'origin/main' into feat/perps/simplify-c…
abretonc7s Apr 1, 2026
769434f
fix(perps): persist preload snapshots for reload hydration
abretonc7s Apr 1, 2026
0411dcf
docs(perps): align preload architecture docs
abretonc7s Apr 1, 2026
9dca23b
fix(perps): address cold-start cache review regressions
abretonc7s Apr 1, 2026
75b569d
Merge branch 'main' into feat/perps/simplify-cache-mechanism
abretonc7s Apr 1, 2026
2e36ef2
Merge branch 'main' into feat/perps/simplify-cache-mechanism
abretonc7s Apr 1, 2026
0479544
fix(perps): use MYX-aware cache key for non-aggregated disk persist
abretonc7s Apr 1, 2026
e1a6ede
Merge remote-tracking branch 'origin/main' into feat/perps/simplify-c…
abretonc7s Apr 2, 2026
ab6ff6a
refactor(perps): address PR review comments
abretonc7s Apr 2, 2026
cb9e5cb
fix: add getSnapshot to view test stream mocks
abretonc7s Apr 2, 2026
4e6a408
Merge remote-tracking branch 'origin/main' into feat/perps/simplify-c…
abretonc7s Apr 2, 2026
3068b3b
fix: add getSnapshot to all stream channel test mocks
abretonc7s Apr 2, 2026
de0cd34
fix(perps): force user data timestamps TTL-stale on disk hydration
abretonc7s Apr 2, 2026
b213d72
refactor(perps): extract disk persistence payload builders to shared …
abretonc7s Apr 2, 2026
fdf68ab
refactor(perps): extract controller disk cache helpers + batch hydrat…
abretonc7s Apr 2, 2026
4fad0a4
refactor(perps): pass persist callbacks via StreamChannel constructor
abretonc7s Apr 2, 2026
5dc4394
fix: use buildProviderCacheKey in MarketDataChannel cache invalidatio…
abretonc7s Apr 2, 2026
95d4fd4
fix(perps): add constructor to StreamChannel in CandleStreamChannel t…
abretonc7s Apr 2, 2026
ff22c28
fix(perps): restore null getSnapshot in channelWithInitialValue test …
abretonc7s Apr 2, 2026
2676c98
Merge branch 'main' into feat/perps/simplify-cache-mechanism
abretonc7s Apr 2, 2026
4ebd01c
Merge branch 'main' into feat/perps/simplify-cache-mechanism
abretonc7s Apr 2, 2026
02cd7d5
Merge branch 'main' into feat/perps/simplify-cache-mechanism
abretonc7s Apr 9, 2026
a7bdbae
chore: formatting
abretonc7s Apr 9, 2026
7aa94cc
Merge branch 'main' into feat/perps/simplify-cache-mechanism
abretonc7s Apr 9, 2026
86efc52
fix: linting
abretonc7s Apr 9, 2026
c242e42
Merge branch 'main' into feat/perps/simplify-cache-mechanism
abretonc7s Apr 9, 2026
69374d8
refactor(perps): extract DISK_HYDRATION_STALENESS_FACTOR constant
abretonc7s Apr 9, 2026
c5c2de6
Merge branch 'main' into feat/perps/simplify-cache-mechanism
abretonc7s Apr 9, 2026
f0c1b67
Merge branch 'main' into feat/perps/simplify-cache-mechanism
abretonc7s Apr 9, 2026
01911c2
Merge branch 'main' into feat/perps/simplify-cache-mechanism
abretonc7s Apr 9, 2026
540930e
Merge branch 'main' into feat/perps/simplify-cache-mechanism
abretonc7s Apr 9, 2026
7c02c83
fix: update E2E fixture for new PerpsController cache fields
abretonc7s Apr 9, 2026
6b62db9
Merge branch 'main' into feat/perps/simplify-cache-mechanism
abretonc7s Apr 9, 2026
472d9d2
Merge branch 'main' into feat/perps/simplify-cache-mechanism
abretonc7s Apr 9, 2026
d3a744d
Merge branch 'main' into feat/perps/simplify-cache-mechanism
abretonc7s Apr 9, 2026
2eb6cbb
Merge remote-tracking branch 'origin/main' into feat/perps/simplify-c…
abretonc7s Apr 9, 2026
cb7332c
fix: add type guard for clearCache in connection manager test
abretonc7s Apr 9, 2026
209555f
Merge branch 'main' into feat/perps/simplify-cache-mechanism
abretonc7s Apr 10, 2026
0ca58a9
Merge branch 'main' into feat/perps/simplify-cache-mechanism
abretonc7s Apr 10, 2026
6d5dd42
test: address bugbot comments on test mocks
abretonc7s Apr 10, 2026
ed61195
Merge remote-tracking branch 'origin/main' into feat/perps/simplify-c…
abretonc7s Apr 10, 2026
ccef9a2
Merge remote-tracking branch 'origin/main' into feat/perps/simplify-c…
abretonc7s Apr 10, 2026
cb7d00b
fix: remove duplicate JSON keys in default fixture
abretonc7s Apr 10, 2026
0c07b5b
Merge remote-tracking branch 'origin/main' into feat/perps/simplify-c…
abretonc7s Apr 10, 2026
6d72c73
test: fix e2e tests
racitores Apr 10, 2026
4c306d2
merge: resolve conflict with origin/main in default-fixture.json
abretonc7s Apr 10, 2026
ed83a41
fix: add perps cached data keys to fixture validation ignore list
abretonc7s Apr 11, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,33 @@ jest.mock('../../providers/PerpsStreamManager', () => ({
prices: {
subscribeToSymbols: jest.fn(() => jest.fn()),
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
positions: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
orders: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
fills: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
account: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
marketData: {
subscribe: jest.fn(() => jest.fn()),
getMarkets: jest.fn(),
getSnapshot: jest.fn(() => null),
},
oiCaps: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
positions: { subscribe: jest.fn(() => jest.fn()) },
orders: { subscribe: jest.fn(() => jest.fn()) },
fills: { subscribe: jest.fn(() => jest.fn()) },
account: { subscribe: jest.fn(() => jest.fn()) },
marketData: { subscribe: jest.fn(() => jest.fn()), getMarkets: jest.fn() },
oiCaps: { subscribe: jest.fn(() => jest.fn()) },
})),
PerpsStreamProvider: ({ children }: { children: React.ReactNode }) =>
children,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -808,18 +808,23 @@ const createMockStreamManager = () => {
};
},
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
orders: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
positions: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
fills: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
account: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
topOfBook: {
subscribeToSymbol: ({
Expand All @@ -842,13 +847,16 @@ const createMockStreamManager = () => {
subscribers.delete(id);
};
},
getSnapshot: jest.fn(() => null),
},
marketData: {
subscribe: jest.fn(() => jest.fn()),
getMarkets: jest.fn(),
getSnapshot: jest.fn(() => null),
},
oiCaps: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,26 @@ jest.mock('../../providers/PerpsStreamManager', () => ({
PerpsStreamProvider: ({ children }: { children: React.ReactNode }) =>
children,
usePerpsStream: jest.fn(() => ({
prices: { subscribe: jest.fn(() => jest.fn()) },
positions: { subscribe: jest.fn(() => jest.fn()) },
orders: { subscribe: jest.fn(() => jest.fn()) },
account: { subscribe: jest.fn(() => jest.fn()) },
marketData: { subscribe: jest.fn(() => jest.fn()) },
prices: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
positions: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
orders: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
account: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
marketData: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
})),
}));

Expand Down
8 changes: 8 additions & 0 deletions app/components/UI/Perps/__mocks__/serviceMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ export const createMockInfrastructure =
rewards: {
getPerpsDiscountForAccount: jest.fn().mockResolvedValue(0),
},

// === Disk Cache (cold-start persistence) ===
diskCache: {
getItem: jest.fn().mockResolvedValue(null),
getItemSync: jest.fn().mockReturnValue(null),
setItem: jest.fn().mockResolvedValue(undefined),
removeItem: jest.fn().mockResolvedValue(undefined),
},
}) as unknown as jest.Mocked<PerpsPlatformDependencies>;

/**
Expand Down
62 changes: 62 additions & 0 deletions app/components/UI/Perps/adapters/mobileInfrastructure.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ jest.mock('../providers/PerpsStreamManager', () => ({
getStreamManagerInstance: jest.fn(),
}));

jest.mock('../../../../store/storage-wrapper', () => ({
__esModule: true,
default: {
getItem: jest.fn().mockResolvedValue('cached-value'),
getItemSync: jest.fn().mockReturnValue('cached-sync-value'),
setItem: jest.fn().mockResolvedValue(undefined),
removeItem: jest.fn().mockResolvedValue(undefined),
},
}));

jest.mock('../../../../core/Engine', () => ({
context: {
RewardsController: {
Expand Down Expand Up @@ -214,6 +224,58 @@ describe('createMobileInfrastructure', () => {
expect(result).toBe(5);
});
});

describe('diskCache', () => {
it('delegates getItem to StorageWrapper.getItem', async () => {
const StorageWrapper = jest.requireMock(
'../../../../store/storage-wrapper',
).default;
const infra = createMobileInfrastructure();

const value = await infra.diskCache.getItem('test-key');

expect(StorageWrapper.getItem).toHaveBeenCalledWith('test-key');
expect(value).toBe('cached-value');
});

it('delegates getItemSync to StorageWrapper.getItemSync', () => {
const StorageWrapper = jest.requireMock(
'../../../../store/storage-wrapper',
).default;
const infra = createMobileInfrastructure();

const value = infra.diskCache.getItemSync?.('test-key');

expect(StorageWrapper.getItemSync).toHaveBeenCalledWith('test-key');
expect(value).toBe('cached-sync-value');
});

it('delegates setItem to StorageWrapper.setItem', async () => {
const StorageWrapper = jest.requireMock(
'../../../../store/storage-wrapper',
).default;
const infra = createMobileInfrastructure();

await infra.diskCache.setItem('test-key', 'test-value');

expect(StorageWrapper.setItem).toHaveBeenCalledWith(
'test-key',
'test-value',
);
});

it('delegates removeItem to StorageWrapper.removeItem', async () => {
const StorageWrapper = jest.requireMock(
'../../../../store/storage-wrapper',
).default;
const infra = createMobileInfrastructure();

const result = await infra.diskCache.removeItem('test-key');

expect(StorageWrapper.removeItem).toHaveBeenCalledWith('test-key');
expect(result).toBeUndefined();
});
});
});

describe('createMobileClientConfig', () => {
Expand Down
11 changes: 11 additions & 0 deletions app/components/UI/Perps/adapters/mobileInfrastructure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import Logger from '../../../../util/Logger';
import StorageWrapper from '../../../../store/storage-wrapper';
import { DevLogger } from '../../../../core/SDKConnect/utils/DevLogger';
import { MetaMetricsEvents } from '../../../../core/Analytics';
import { AnalyticsEventBuilder } from '../../../../util/analytics/AnalyticsEventBuilder';
Expand Down Expand Up @@ -287,6 +288,16 @@ export function createMobileInfrastructure(): PerpsPlatformDependencies {
// === Cache Invalidation ===
cacheInvalidator: createCacheInvalidatorAdapter(),

// === Disk Cache (cold-start persistence via MMKV) ===
diskCache: {
getItem: (key: string) => StorageWrapper.getItem(key),
getItemSync: (key: string) => StorageWrapper.getItemSync(key),
setItem: (key: string, value: string) =>
StorageWrapper.setItem(key, value),
removeItem: (key: string) =>
StorageWrapper.removeItem(key).then(() => undefined),
},

// === Rewards (DI — no RewardsController in Core yet) ===
rewards: {
getPerpsDiscountForAccount(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const createMockStreamManager = (): Partial<PerpsStreamManager> => ({
cleanupPrewarm: jest.fn(),
clearCache: jest.fn(),
disconnect: jest.fn(),
getSnapshot: jest.fn(() => null),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,

Expand All @@ -76,6 +77,7 @@ const createMockStreamManager = (): Partial<PerpsStreamManager> => ({
cleanupPrewarm: jest.fn(),
clearCache: jest.fn(),
disconnect: jest.fn(),
getSnapshot: jest.fn(() => null),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,

Expand All @@ -86,6 +88,7 @@ const createMockStreamManager = (): Partial<PerpsStreamManager> => ({
cleanupPrewarm: jest.fn(),
clearCache: jest.fn(),
disconnect: jest.fn(),
getSnapshot: jest.fn(() => null),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,

Expand All @@ -96,6 +99,7 @@ const createMockStreamManager = (): Partial<PerpsStreamManager> => ({
cleanupPrewarm: jest.fn(),
clearCache: jest.fn(),
disconnect: jest.fn(),
getSnapshot: jest.fn(() => null),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,

Expand All @@ -106,6 +110,7 @@ const createMockStreamManager = (): Partial<PerpsStreamManager> => ({
cleanupPrewarm: jest.fn(),
clearCache: jest.fn(),
disconnect: jest.fn(),
getSnapshot: jest.fn(() => null),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,

Expand All @@ -115,6 +120,7 @@ const createMockStreamManager = (): Partial<PerpsStreamManager> => ({
refresh: jest.fn(() => Promise.resolve()),
prewarm: jest.fn(() => jest.fn()),
clearCache: jest.fn(),
getSnapshot: jest.fn(() => null),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,27 @@ jest.mock('../../providers/PerpsStreamManager', () => ({
usePerpsStream: jest.fn(() => ({
account: {
subscribe: jest.fn(() => jest.fn()), // Returns unsubscribe function
getSnapshot: jest.fn(() => null),
},
positions: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
orders: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
fills: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
prices: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
marketData: {
subscribe: jest.fn(() => jest.fn()),
getSnapshot: jest.fn(() => null),
},
})),
PerpsStreamProvider: ({ children }: { children: React.ReactNode }) =>
Expand Down
7 changes: 7 additions & 0 deletions app/components/UI/Perps/constants/perpsConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,13 @@ export function getPerpsProviderChainId(
return PERPS_PROVIDER_CHAIN_IDS[provider]?.[network];
}

// Re-export disk cache constants from controller layer
export {
PERPS_DISK_CACHE_MARKETS,
PERPS_DISK_CACHE_USER_DATA,
PERPS_DISK_CACHE_THROTTLE_MS,
} from '@metamask/perps-controller/constants/perpsConfig';

/** Source identifiers for PerpsConnectionManager.connect/ensureConnected/resumeFromForeground calls. */
export const PERPS_CONNECTION_SOURCE = {
WALLET_ROOT_MOUNT: 'wallet_root_mount',
Expand Down
Loading
Loading