Skip to content
35 changes: 8 additions & 27 deletions app/components/UI/Perps/hooks/usePerpsWatchlistActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,22 @@ interface UsePerpsWatchlistActionsResult {
/**
* Adds a market to the watchlist.
*
* Currently wraps the synchronous PerpsController.toggleWatchlistMarket call.
*
* TODO(TAT-2663 — optimistic UI): Before the await, apply an optimistic local
* state update (e.g. pass an onOptimisticUpdate callback from the caller).
* On failure, roll back that state and show the addError toast below.
*
* TODO(TAT-2663 — User Storage API): Replace / augment the controller call
* with a POST to the User Storage API once it is available.
* Delegates to PerpsController.toggleWatchlistMarket, which performs an
* optimistic local update and syncs to AuthenticatedUserStorageService.
* Falls back to local-only when unauthenticated.
*/
addToWatchlist: (symbol: string) => Promise<void>;
/**
* Removes a market from the watchlist.
*
* Same optimistic UI and User Storage seams as addToWatchlist above.
* Same optimistic-update and AUS sync behaviour as addToWatchlist.
*/
removeFromWatchlist: (symbol: string) => Promise<void>;
}

/**
* Provides add/remove watchlist actions with analytics, error reporting,
* and structural seams for optimistic UI and the future User Storage API.
* Provides add/remove watchlist actions with analytics and error reporting.
* The controller handles optimistic UI, AUS sync, and graceful degradation.
*
* @param source - Analytics source identifying where the action was triggered
* (e.g. PERPS_EVENT_VALUE.SOURCE.PERPS_HOME_WATCHLIST).
Expand All @@ -57,12 +52,7 @@ export const usePerpsWatchlistActions = (
return;
}

// TODO(TAT-2663 — optimistic UI): Call onOptimisticUpdate?.() here
// to instantly reflect the add in the UI before the async call resolves.

controller.toggleWatchlistMarket(symbol);

// TODO(TAT-2663 — User Storage API): await userStorageApi.addWatchlistMarket(symbol)
await controller.toggleWatchlistMarket(symbol);

const watchlistCount = controller.getWatchlistMarkets().length;
track(MetaMetricsEvents.PERPS_UI_INTERACTION, {
Expand All @@ -75,9 +65,6 @@ export const usePerpsWatchlistActions = (
[PERPS_EVENT_PROPERTY.FAVORITES_COUNT]: watchlistCount,
});
} catch (error) {
// TODO(TAT-2663 — optimistic UI): Roll back the optimistic state
// update here (e.g. call onRollback?.()) before showing the error toast.

Logger.error(ensureError(error, 'usePerpsWatchlistActions.add'), {
tags: {
feature: PERPS_CONSTANTS.FeatureName,
Expand All @@ -99,12 +86,8 @@ export const usePerpsWatchlistActions = (
const removeFromWatchlist = useCallback(
async (symbol: string): Promise<void> => {
try {
// TODO(TAT-2663 — optimistic UI): Apply optimistic remove here.

const controller = Engine.context.PerpsController;
controller.toggleWatchlistMarket(symbol);

// TODO(TAT-2663 — User Storage API): await userStorageApi.removeWatchlistMarket(symbol)
await controller.toggleWatchlistMarket(symbol);
Comment thread
cursor[bot] marked this conversation as resolved.

const watchlistCount = controller.getWatchlistMarkets().length;
track(MetaMetricsEvents.PERPS_UI_INTERACTION, {
Expand All @@ -117,8 +100,6 @@ export const usePerpsWatchlistActions = (
[PERPS_EVENT_PROPERTY.FAVORITES_COUNT]: watchlistCount,
});
} catch (error) {
// TODO(TAT-2663 — optimistic UI): Roll back the optimistic remove here.

Logger.error(ensureError(error, 'usePerpsWatchlistActions.remove'), {
tags: {
feature: PERPS_CONSTANTS.FeatureName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,22 @@ describe('PerpsController Messenger', () => {
}),
);
});

it('delegates AUS notification-preference actions to the perps controller messenger', () => {
const baseControllerMessenger = new ExtendedMessenger<MockAnyNamespace>({
namespace: MOCK_ANY_NAMESPACE,
});
const delegateSpy = jest.spyOn(baseControllerMessenger, 'delegate');

getPerpsControllerMessenger(baseControllerMessenger);

expect(delegateSpy).toHaveBeenCalledWith(
expect.objectContaining({
actions: expect.arrayContaining([
'AuthenticatedUserStorageService:getNotificationPreferences',
'AuthenticatedUserStorageService:putNotificationPreferences',
]),
}),
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
* PerpsController uses the messenger for all cross-controller communication:
* NetworkController, KeyringController, TransactionController,
* RemoteFeatureFlagController, AccountsController, AccountTreeController,
* AuthenticationController.
* AuthenticationController, AuthenticatedUserStorageService.
* The root messenger already registers actions for these controllers,
* so the child messenger can call them through the parent.
*
Expand All @@ -31,6 +31,11 @@ export function getPerpsControllerMessenger(
namespace: 'PerpsController',
parent: rootExtendedMessenger,
});
// TS2590: The union of PerpsControllerAllowedActions & GlobalActions exceeds
// TypeScript's complexity limit. The delegate call is correct at runtime;
// suppress the compiler error rather than weakening the types.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error TS2590
Comment thread
gambinish marked this conversation as resolved.
Outdated
rootExtendedMessenger.delegate({
actions: [
'GeolocationController:getGeolocation',
Expand All @@ -44,6 +49,8 @@ export function getPerpsControllerMessenger(
'AccountsController:getSelectedAccount',
'AccountTreeController:getAccountsFromSelectedAccountGroup',
'AuthenticationController:getBearerToken',
'AuthenticatedUserStorageService:getNotificationPreferences',
'AuthenticatedUserStorageService:putNotificationPreferences',
],
events: [
'RemoteFeatureFlagController:stateChange',
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@
]
},
"resolutions": {
"@metamask/perps-controller": "npm:@metamask-previews/perps-controller@8.0.0-preview-7bc7196a6",
"@metamask/network-controller": "32.0.0",
"@react-native-community/viewpager": "patch:@react-native-community/viewpager@npm%3A3.3.0#~/.yarn/patches/@react-native-community-viewpager-npm-3.3.0.patch",
"@solana-mobile/mobile-wallet-adapter-protocol": "^2.2.5",
Expand Down
12 changes: 6 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8308,7 +8308,7 @@ __metadata:
languageName: node
linkType: hard

"@metamask/controller-utils@npm:^12.0.0, @metamask/controller-utils@npm:^12.1.0, @metamask/controller-utils@npm:^12.1.1, @metamask/controller-utils@npm:^12.2.0":
"@metamask/controller-utils@npm:^12.0.0, @metamask/controller-utils@npm:^12.1.0, @metamask/controller-utils@npm:^12.1.1":
version: 12.2.0
resolution: "@metamask/controller-utils@npm:12.2.0"
dependencies:
Expand Down Expand Up @@ -9471,13 +9471,13 @@ __metadata:
languageName: node
linkType: hard

"@metamask/perps-controller@npm:^8.1.0":
version: 8.1.0
resolution: "@metamask/perps-controller@npm:8.1.0"
"@metamask/perps-controller@npm:@metamask-previews/perps-controller@8.0.0-preview-7bc7196a6":
version: 8.0.0-preview-7bc7196a6
resolution: "@metamask-previews/perps-controller@npm:8.0.0-preview-7bc7196a6"
dependencies:
"@metamask/abi-utils": "npm:^2.0.3"
"@metamask/base-controller": "npm:^9.1.0"
"@metamask/controller-utils": "npm:^12.2.0"
"@metamask/controller-utils": "npm:^12.1.1"
"@metamask/messenger": "npm:^1.2.0"
"@metamask/utils": "npm:^11.9.0"
"@myx-trade/sdk": "npm:^0.1.265"
Expand All @@ -9488,7 +9488,7 @@ __metadata:
dependenciesMeta:
"@myx-trade/sdk":
optional: true
checksum: 10/adb4501e0d5fcb8837ea87bdea43bc5c0444838d218d0cdd3d3153fb3dc25307906e5078f7ac38f2799569bb7d82c64c3d352b78d77829da4068275c5dec6849
checksum: 10/b570e2ee8cf76502ca8032b54ec1cd2f2c260c3791cf986bdd9c65b86f6053eb6161ee5d9728de52820116b5478177ad8ae349a6888f9c48641241204c3e87a1
languageName: node
linkType: hard

Expand Down
Loading