Skip to content

Commit dd7eb4b

Browse files
chore(runway): cherry-pick fix(card): update feature flag listener on CardController (#29376)
- fix(card): update feature flag listener on CardController (#29350) <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> ## **Description** This branch refreshes **card home data and Baanx configuration** when **remote card feature flags** change, and fixes a **race** where a slow **unauthenticated** `fetchCardHomeData` could overwrite state **after** successful **submitCredentials**. **Feature flags** - [`app/selectors/featureFlagController/card/index.ts`](app/selectors/featureFlagController/card/index.ts): exports **`defaultCardFeatureFlag`** and **`resolveCardFeatureFlag`** (empty remote payload → defaults). **`selectCardFeatureFlag`** now uses **`resolveCardFeatureFlag`** so the same resolution rules apply in Redux selectors and Engine. **BaanxProvider / init** - [`BaanxProvider.ts`](app/core/Engine/controllers/card-controller/providers/BaanxProvider.ts): accepts optional **`getCardFeatureFlag`** (lazy) in addition to legacy **`cardFeatureFlag`**. A private getter resolves **`cardFeatureFlag`** on each read so URLs/constants track the latest remote flags without recreating the provider. - [`card-controller/index.ts`](app/core/Engine/controllers/card-controller/index.ts): passes **`getCardFeatureFlag`** from **`RemoteFeatureFlagController:getState`** + **`resolveCardFeatureFlag`** into **`BaanxProvider`**. **CardController** - Subscribes to **`RemoteFeatureFlagController:stateChange`** with a **selector** that serializes **`remoteFeatureFlags.cardFeature`** so only meaningful card-flag updates run the handler. - **`#handleCardFeatureFlagChange`**: if an EVM address is selected, **`invalidateFetch()`**, clears **`cardHomeData`** / sets **`cardHomeDataStatus`** to **`idle`**, then refetches card home data. - **`#fetchCardHomeDataWithLogging`**: centralizes **`fetchCardHomeData`** + **`Logger.error`** for account switch, feature-flag refresh, **`submitCredentials`**, and **`validateAndRefreshSession`**. - **`#triggerCardholderCheck`** (accounts API URL path): uses **`resolveCardFeatureFlag`** instead of ad-hoc casting. - **`submitCredentials`**: sets **`cardHomeData`** to **`null`** and status **`idle`**, calls **`invalidateFetch()`**, then fetches—so in-flight unauthenticated responses cannot win over authenticated data. **Messenger / types** - [`card-controller-messenger/index.ts`](app/core/Engine/messengers/card-controller-messenger/index.ts): allows event **`RemoteFeatureFlagController:stateChange`**. - [`types.ts`](app/core/Engine/controllers/card-controller/types.ts): **`CardControllerAllowedEvents`** includes **`RemoteFeatureFlagControllerStateChangeEvent`**. **Tests** - [`CardController.test.ts`](app/core/Engine/controllers/card-controller/CardController.test.ts): subscription to remote feature-flag state; handler clears state and refetches; **`drops in-flight unauthenticated card home data after successful auth`**. - [`BaanxProvider.test.ts`](app/core/Engine/controllers/card-controller/providers/BaanxProvider.test.ts): **`getCardFeatureFlag`** read **lazily** during **`getOnChainAssets`**. ## **Changelog** CHANGELOG entry: Card — **`CardController`** listens for **`RemoteFeatureFlagController:stateChange`** and refetches card home data when **`cardFeature`** changes; **`BaanxProvider`** resolves card feature flags lazily; shared **`resolveCardFeatureFlag`**; **`submitCredentials`** invalidates in-flight fetches and resets card home state to avoid stale unauthenticated data after login. ## **Related issues** Fixes: #29348 <!-- Add GitHub issue link when available, e.g. Fixes #12345 --> ## **Manual testing steps** ```gherkin Feature: Card home and Baanx flags stay in sync with remote card feature flags Scenario: Remote card feature flag updates while card tab is open Given the user is on an EVM account with card session and card home loaded When remote `cardFeature` changes (e.g. rollout or config update) Then card home data is cleared to idle and refetched And Baanx-backed calls use the updated flag-derived config on subsequent requests Scenario: Login while an unauthenticated card home fetch is still in flight Given an unauthenticated card home fetch has not completed When the user completes submitCredentials successfully Then authenticated card home data wins and a late unauthenticated response does not overwrite it Scenario: Card feature flag change with no selected EVM address When RemoteFeatureFlagController emits a card-related change but no EVM address is selected Then the handler returns early without refetch errors ``` ## **Screenshots/Recordings** <!-- Optional: behavior is mostly data-layer; attach if you verify on device after a forced remote flag change. --> ### **Before** <!-- Card home could stay stale until manual refresh; rare stale data after fast login during slow unauthenticated fetch. --> ### **After** <!-- Card home refetches when `cardFeature` changes; submitCredentials path drops stale in-flight unauthenticated results. --> ## **Pre-merge author checklist** <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Updates card state refresh triggers and fetch invalidation logic around auth and feature-flag changes; risk is moderate due to new event subscriptions and potential for extra network calls or missed updates if selectors are wrong. > > **Overview** > **Card home data now refreshes when remote `cardFeature` flags change.** `CardController` subscribes to `RemoteFeatureFlagController:stateChange`, clears `cardHomeData`/status back to `idle`, invalidates any in-flight fetch, and refetches. > > **Fixes a race where slow unauthenticated fetches could overwrite authenticated state.** After successful `submitCredentials`, the controller resets home data, bumps the fetch generation, and refetches via a shared `#fetchCardHomeDataWithLogging` helper (also used by account-switch and session refresh paths). > > **Feature-flag resolution and consumption were tightened.** Adds `resolveCardFeatureFlag` (and exports `defaultCardFeatureFlag`) to normalize empty remote payloads to defaults; `BaanxProvider` can now read flags lazily via a `getCardFeatureFlag` callback wired in `cardControllerInit`, and the card-controller messenger/types allow the new remote-flag stateChange event. > > Tests add coverage for the new subscription behavior, the stale-fetch drop after auth, and lazy flag reads in `BaanxProvider`. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 0d9863b. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> [890ac4a](890ac4a) Co-authored-by: Bruno Nascimento <brunonascimentodev@gmail.com>
1 parent dbab42c commit dd7eb4b

8 files changed

Lines changed: 225 additions & 45 deletions

File tree

app/core/Engine/controllers/card-controller/CardController.test.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ jest.mock('../../../../util/Logger');
2424

2525
const mockTokenStore = CardTokenStore as jest.Mocked<typeof CardTokenStore>;
2626

27+
async function flushPromises(times = 5): Promise<void> {
28+
for (let i = 0; i < times; i++) {
29+
await Promise.resolve();
30+
}
31+
}
32+
2733
function buildMessenger() {
2834
return new Messenger<
2935
'CardController',
@@ -314,6 +320,71 @@ describe('CardController — auth methods', () => {
314320
expect(result.done).toBe(true);
315321
});
316322

323+
it('drops in-flight unauthenticated card home data after successful auth', async () => {
324+
const staleAsset = { ...mockAsset, symbol: 'STALE' };
325+
const staleHomeData = {
326+
...mockCardHomeData,
327+
primaryFundingAsset: staleAsset,
328+
fundingAssets: [staleAsset],
329+
};
330+
const authenticatedAsset = { ...mockAsset, symbol: 'AUTH' };
331+
const authenticatedHomeData = {
332+
...mockCardHomeData,
333+
primaryFundingAsset: authenticatedAsset,
334+
fundingAssets: [authenticatedAsset],
335+
};
336+
let resolveStaleFetch: (data: CardHomeData) => void = () => undefined;
337+
338+
const provider = buildMockProvider();
339+
const getOnChainAssetsMock =
340+
provider.getOnChainAssets as jest.MockedFunction<
341+
NonNullable<ICardProvider['getOnChainAssets']>
342+
>;
343+
provider.initiateAuth.mockResolvedValue(mockSession);
344+
provider.submitCredentials.mockResolvedValue({
345+
done: true,
346+
tokenSet: mockTokenSet,
347+
});
348+
getOnChainAssetsMock.mockReturnValue(
349+
new Promise<CardHomeData>((resolve) => {
350+
resolveStaleFetch = resolve;
351+
}),
352+
);
353+
provider.validateTokens.mockReturnValue('valid');
354+
provider.getCardHomeData.mockResolvedValue(authenticatedHomeData);
355+
mockTokenStore.get
356+
.mockResolvedValueOnce(null)
357+
.mockResolvedValue(mockTokenSet);
358+
mockTokenStore.set.mockResolvedValue(true);
359+
360+
const { controller } = buildControllerWithMockMessenger(provider);
361+
const staleFetchPromise = controller.fetchCardHomeData();
362+
await flushPromises();
363+
364+
expect(getOnChainAssetsMock).toHaveBeenCalledTimes(1);
365+
366+
await controller.initiateAuth('US');
367+
await controller.submitCredentials({
368+
type: 'email_password',
369+
email: 'a@b.com',
370+
password: 'pass',
371+
});
372+
await flushPromises();
373+
374+
expect(provider.getCardHomeData).toHaveBeenCalledTimes(1);
375+
expect(controller.state.cardHomeData).toStrictEqual(
376+
authenticatedHomeData,
377+
);
378+
379+
resolveStaleFetch(staleHomeData);
380+
await staleFetchPromise;
381+
await flushPromises();
382+
383+
expect(controller.state.cardHomeData).toStrictEqual(
384+
authenticatedHomeData,
385+
);
386+
});
387+
317388
it('still sets isAuthenticated when token store write fails', async () => {
318389
const provider = buildMockProvider();
319390
provider.initiateAuth.mockResolvedValue(mockSession);
@@ -646,6 +717,46 @@ describe('CardController — event subscriptions', () => {
646717
);
647718
});
648719

720+
it('subscribes to RemoteFeatureFlagController:stateChange on construction', () => {
721+
const mockMessenger = buildMockMessenger();
722+
new CardController({
723+
messenger: mockMessenger,
724+
providers: {},
725+
});
726+
727+
expect(mockMessenger.subscribe).toHaveBeenCalledWith(
728+
'RemoteFeatureFlagController:stateChange',
729+
expect.any(Function),
730+
expect.any(Function),
731+
);
732+
});
733+
734+
it('clears and refetches card home data when the card feature flag changes', () => {
735+
const provider = buildMockProvider();
736+
const { controller, messenger } = buildControllerWithMockMessenger(
737+
provider,
738+
{
739+
cardHomeData: mockCardHomeData as unknown as Record<string, null>,
740+
cardHomeDataStatus: 'success',
741+
},
742+
);
743+
const fetchSpy = jest
744+
.spyOn(controller, 'fetchCardHomeData')
745+
.mockResolvedValue();
746+
747+
const remoteFeatureFlagHandler = (
748+
messenger.subscribe as jest.Mock
749+
).mock.calls.find(
750+
([event]) => event === 'RemoteFeatureFlagController:stateChange',
751+
)?.[1];
752+
753+
remoteFeatureFlagHandler?.('{}');
754+
755+
expect(controller.state.cardHomeData).toBeNull();
756+
expect(controller.state.cardHomeDataStatus).toBe('idle');
757+
expect(fetchSpy).toHaveBeenCalledTimes(1);
758+
});
759+
649760
it('calls validateAndRefreshSession on KeyringController:unlock', async () => {
650761
const provider = buildMockProvider();
651762
mockTokenStore.get.mockResolvedValue(null);

app/core/Engine/controllers/card-controller/CardController.ts

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ import {
3737
import { CardTokenStore } from './CardTokenStore';
3838
import { isEthAccount } from '../../../Multichain/utils';
3939
import { pickPrimaryFromReordered, reorderAssets } from './utils/assetPriority';
40+
import {
41+
resolveCardFeatureFlag,
42+
type CardFeatureFlag,
43+
} from '../../../../selectors/featureFlagController/card';
4044

4145
const CARDHOLDER_BATCH_SIZE = 50;
4246
const CARDHOLDER_MAX_BATCHES = 3;
@@ -170,6 +174,26 @@ export class CardController extends BaseController<
170174
.sort()
171175
.join(','),
172176
);
177+
178+
this.messenger.subscribe(
179+
'RemoteFeatureFlagController:stateChange',
180+
(_cardFeatureKey: string) => {
181+
this.#handleCardFeatureFlagChange();
182+
},
183+
(state) => JSON.stringify(state.remoteFeatureFlags?.cardFeature ?? {}),
184+
);
185+
}
186+
187+
#fetchCardHomeDataWithLogging(method: string): void {
188+
this.fetchCardHomeData().catch((error) =>
189+
Logger.error(error as Error, {
190+
tags: { feature: 'card' },
191+
context: {
192+
name: 'CardController',
193+
data: { method },
194+
},
195+
}),
196+
);
173197
}
174198

175199
#handleAccountSwitch(): void {
@@ -182,18 +206,22 @@ export class CardController extends BaseController<
182206
s.cardHomeData = null;
183207
s.cardHomeDataStatus = 'idle';
184208
});
185-
this.fetchCardHomeData().catch((error) =>
186-
Logger.error(error as Error, {
187-
tags: { feature: 'card' },
188-
context: {
189-
name: 'CardController',
190-
data: { method: '#handleAccountSwitch' },
191-
},
192-
}),
193-
);
209+
this.#fetchCardHomeDataWithLogging('#handleAccountSwitch');
194210
}
195211
}
196212

213+
#handleCardFeatureFlagChange(): void {
214+
const currentAddress = this.#getSelectedEvmAddress();
215+
if (!currentAddress) return;
216+
217+
this.invalidateFetch();
218+
this.update((s) => {
219+
s.cardHomeData = null;
220+
s.cardHomeDataStatus = 'idle';
221+
});
222+
this.#fetchCardHomeDataWithLogging('#handleCardFeatureFlagChange');
223+
}
224+
197225
#triggerCardholderCheck(): void {
198226
// Debounce: coalesce rapid consecutive triggers (e.g. KeyringController:unlock
199227
// and AccountTreeController:stateChange both fire on wallet unlock) into a
@@ -220,9 +248,11 @@ export class CardController extends BaseController<
220248
const featureState = this.messenger.call(
221249
'RemoteFeatureFlagController:getState',
222250
);
223-
const cardFeature = featureState.remoteFeatureFlags?.cardFeature as
224-
| { constants?: { accountsApiUrl?: string } }
225-
| undefined;
251+
const cardFeature = resolveCardFeatureFlag(
252+
featureState.remoteFeatureFlags?.cardFeature as
253+
| CardFeatureFlag
254+
| undefined,
255+
);
226256
const accountsApiUrl = cardFeature?.constants?.accountsApiUrl;
227257
if (!accountsApiUrl) return;
228258

@@ -475,19 +505,14 @@ export class CardController extends BaseController<
475505
}
476506
this.update((s) => {
477507
s.isAuthenticated = true;
508+
s.cardHomeData = null;
509+
s.cardHomeDataStatus = 'idle';
478510
(s.providerData as unknown as Record<string, Record<string, string>>)[
479511
pid
480512
] = { location: tokenSet.location };
481513
});
482-
this.fetchCardHomeData().catch((error) =>
483-
Logger.error(error as Error, {
484-
tags: { feature: 'card' },
485-
context: {
486-
name: 'CardController',
487-
data: { method: 'submitCredentials/fetchCardHomeData' },
488-
},
489-
}),
490-
);
514+
this.invalidateFetch();
515+
this.#fetchCardHomeDataWithLogging('submitCredentials/fetchCardHomeData');
491516
}
492517

493518
return result;
@@ -541,14 +566,8 @@ export class CardController extends BaseController<
541566

542567
// Always fetch card home data regardless of auth state: authenticated users
543568
// get full card data, unauthenticated users get on-chain asset state.
544-
this.fetchCardHomeData().catch((error) =>
545-
Logger.error(error as Error, {
546-
tags: { feature: 'card' },
547-
context: {
548-
name: 'CardController',
549-
data: { method: 'validateAndRefreshSession/fetchCardHomeData' },
550-
},
551-
}),
569+
this.#fetchCardHomeDataWithLogging(
570+
'validateAndRefreshSession/fetchCardHomeData',
552571
);
553572

554573
if (!tokens) return { isAuthenticated: false };

app/core/Engine/controllers/card-controller/index.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import type { CardControllerMessenger } from './types';
44
import { BaanxService } from './services/BaanxService';
55
import { BaanxProvider } from './providers/BaanxProvider';
66
import { resolveBaanxConfig } from './services/baanx-config';
7-
import type { CardFeatureFlag } from '../../../../selectors/featureFlagController/card';
7+
import {
8+
resolveCardFeatureFlag,
9+
type CardFeatureFlag,
10+
} from '../../../../selectors/featureFlagController/card';
811

912
/**
1013
* Initialize the CardController.
@@ -18,17 +21,21 @@ export const cardControllerInit: MessengerClientInitFunction<
1821
> = (request) => {
1922
const { controllerMessenger, persistedState } = request;
2023

21-
const featureState = controllerMessenger.call(
22-
'RemoteFeatureFlagController:getState',
23-
);
24-
const cardFeatureFlag = featureState.remoteFeatureFlags?.cardFeature as
25-
| CardFeatureFlag
26-
| undefined;
24+
const getCardFeatureFlag = () => {
25+
const featureState = controllerMessenger.call(
26+
'RemoteFeatureFlagController:getState',
27+
);
28+
return resolveCardFeatureFlag(
29+
featureState.remoteFeatureFlags?.cardFeature as
30+
| CardFeatureFlag
31+
| undefined,
32+
);
33+
};
2734

2835
const baanxConfig = resolveBaanxConfig();
2936
const baanxProvider = new BaanxProvider({
3037
service: new BaanxService(baanxConfig),
31-
cardFeatureFlag,
38+
getCardFeatureFlag,
3239
});
3340

3441
const controller = new CardController({

app/core/Engine/controllers/card-controller/providers/BaanxProvider.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,27 @@ describe('BaanxProvider', () => {
688688
);
689689
});
690690

691+
it('reads card feature flags lazily when checking on-chain assets', async () => {
692+
spendersMock.mockResolvedValue([limitedTuple('50'), limitedTuple('0')]);
693+
let currentCardFeatureFlag: CardFeatureFlag | null = null;
694+
695+
const p = new BaanxProvider({
696+
service,
697+
getCardFeatureFlag: () => currentCardFeatureFlag,
698+
});
699+
currentCardFeatureFlag = cardFeatureFlag;
700+
701+
const result = await p.getOnChainAssets(ownerAddr);
702+
703+
expect(spendersMock).toHaveBeenCalledWith(
704+
ownerAddr,
705+
[tokenA, tokenB],
706+
expect.any(Array),
707+
);
708+
expect(result.primaryFundingAsset?.symbol).toBe('USDC');
709+
expect(result.primaryFundingAsset?.address).toBe(tokenA);
710+
});
711+
691712
it('uses #findLastApprovedToken when multiple tokens have non-zero allowance and prefers latest Approval log', async () => {
692713
spendersMock.mockResolvedValue([limitedTuple('10'), limitedTuple('20')]);
693714

app/core/Engine/controllers/card-controller/providers/BaanxProvider.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,17 +247,24 @@ export class BaanxProvider implements ICardProvider {
247247
}
248248

249249
private readonly service: BaanxService;
250-
private readonly cardFeatureFlag: CardFeatureFlag | null;
250+
private readonly getCardFeatureFlag: () => CardFeatureFlag | null;
251251

252252
constructor({
253253
service,
254254
cardFeatureFlag,
255+
getCardFeatureFlag,
255256
}: {
256257
service: BaanxService;
257258
cardFeatureFlag?: CardFeatureFlag;
259+
getCardFeatureFlag?: () => CardFeatureFlag | null | undefined;
258260
}) {
259261
this.service = service;
260-
this.cardFeatureFlag = cardFeatureFlag ?? null;
262+
this.getCardFeatureFlag = () =>
263+
getCardFeatureFlag?.() ?? cardFeatureFlag ?? null;
264+
}
265+
266+
private get cardFeatureFlag(): CardFeatureFlag | null {
267+
return this.getCardFeatureFlag();
261268
}
262269

263270
// -- Auth --

app/core/Engine/controllers/card-controller/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import type {
1313
KeyringControllerUnlockEvent,
1414
KeyringControllerSignPersonalMessageAction,
1515
} from '@metamask/keyring-controller';
16-
import type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller';
16+
import type {
17+
RemoteFeatureFlagControllerGetStateAction,
18+
RemoteFeatureFlagControllerStateChangeEvent,
19+
} from '@metamask/remote-feature-flag-controller';
1720
import type { NetworkControllerFindNetworkClientIdByChainIdAction } from '@metamask/network-controller';
1821
import type { TransactionControllerAddTransactionAction } from '@metamask/transaction-controller';
1922
import type { CardHomeData } from './provider-types';
@@ -71,6 +74,7 @@ type CardControllerAllowedActions =
7174

7275
type CardControllerAllowedEvents =
7376
| AccountTreeControllerStateChangeEvent
77+
| RemoteFeatureFlagControllerStateChangeEvent
7478
| KeyringControllerUnlockEvent;
7579

7680
export type CardControllerMessenger = Messenger<

app/core/Engine/messengers/card-controller-messenger/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ export function getCardControllerMessenger(
3535
'NetworkController:findNetworkClientIdByChainId',
3636
'TransactionController:addTransaction',
3737
],
38-
events: ['AccountTreeController:stateChange', 'KeyringController:unlock'],
38+
events: [
39+
'AccountTreeController:stateChange',
40+
'RemoteFeatureFlagController:stateChange',
41+
'KeyringController:unlock',
42+
],
3943
});
4044

4145
return messenger;

0 commit comments

Comments
 (0)