Skip to content

Commit 139b310

Browse files
micaelaesalimtb
andauthored
fix: cp-7.57.0 update destination chain balances after bridge transaction (#21430)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Changes - patch bridge-status-controller so that it publishes the `destTransactionCompleted` event with the received token's CAIP assetId when the bridge-api indicates that a bridge transaction has successfully completed <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: `fix: trigger token balance update on destination chain after a bridge transaction succeeds` ## **Related issues** Fixes: #20727 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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. ## **Pre-merge reviewer checklist** - [ ] 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] > Emit a destination-transaction-completed event with CAIP asset from BridgeStatusController and have Engine refresh EVM token detection/balances upon receipt; update token refresh flow to use Solana selection and add a null-safe log topic check. > > - **Bridge/Status**: > - Publish `BridgeStatusController:destinationTransactionCompleted` with received `CaipAssetType` when status is `COMPLETE` in `dist/bridge-status-controller.cjs`. > - Extend `dist/types.d.cts` with the new event type and payload. > - **Engine**: > - Subscribe to `BridgeStatusController:destinationTransactionCompleted` and, for `eip155` chains, trigger `TokenDetectionController.detectTokens` and `TokenBalancesController.updateBalances` for the destination chain in `app/core/Engine/Engine.ts`. > - **Tokens**: > - Refactor `refreshTokens` API to `isSolanaSelected` (non‑EVM path updates `MultichainBalancesController`; EVM path uses `performEvmRefresh`). > - Update `app/components/UI/Tokens/index.tsx` to derive `isSolanaSelected` and pass it to `refreshTokens`. > - Update tests in `util/refreshTokens.test.ts` to reflect new logic and expectations. > - **Utils**: > - Add optional chaining to `topics[0]?.startsWith(...)` in `dist/utils/swap-received-amount.cjs` to avoid undefined access. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 3c913cc. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: salimtb <salim.toubal@consensys.net> Co-authored-by: Salim TOUBAL <salim.toubal@outlook.com>
1 parent 95d2b85 commit 139b310

File tree

8 files changed

+94
-38
lines changed

8 files changed

+94
-38
lines changed

.yarn/patches/@metamask-bridge-status-controller-npm-47.2.0-1c8660e896.patch

Lines changed: 0 additions & 13 deletions
This file was deleted.

.yarn/patches/@metamask-bridge-status-controller-npm-50.1.0-308ed9262e.patch

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,38 @@
1+
diff --git a/dist/bridge-status-controller.cjs b/dist/bridge-status-controller.cjs
2+
index 6aabde7229d9c6ce349b148a16b5e8857f9670be..52f87a160c701c8e38dabf74b5043b9781fad9f5 100644
3+
--- a/dist/bridge-status-controller.cjs
4+
+++ b/dist/bridge-status-controller.cjs
5+
@@ -334,6 +334,7 @@ class BridgeStatusController extends (0, polling_controller_1.StaticIntervalPoll
6+
}
7+
if (status.status === bridge_controller_1.StatusTypes.COMPLETE) {
8+
__classPrivateFieldGet(this, _BridgeStatusController_trackUnifiedSwapBridgeEvent, "f").call(this, bridge_controller_1.UnifiedSwapBridgeEventName.Completed, bridgeTxMetaId);
9+
+ this.messagingSystem.publish('BridgeStatusController:destinationTransactionCompleted', historyItem.quote.destAsset.assetId);
10+
}
11+
if (status.status === bridge_controller_1.StatusTypes.FAILED) {
12+
__classPrivateFieldGet(this, _BridgeStatusController_trackUnifiedSwapBridgeEvent, "f").call(this, bridge_controller_1.UnifiedSwapBridgeEventName.Failed, bridgeTxMetaId);
13+
diff --git a/dist/types.d.cts b/dist/types.d.cts
14+
index 047c064a382ba909ef1801aeb2b46fbbdc0991c0..287eb8459fa4e75744b7e575d2330547cf8c561a 100644
15+
--- a/dist/types.d.cts
16+
+++ b/dist/types.d.cts
17+
@@ -176,7 +176,15 @@ export type BridgeStatusControllerRestartPollingForFailedAttemptsAction = Bridge
18+
export type BridgeStatusControllerGetBridgeHistoryItemByTxMetaIdAction = BridgeStatusControllerAction<BridgeStatusAction.GET_BRIDGE_HISTORY_ITEM_BY_TX_META_ID>;
19+
export type BridgeStatusControllerActions = BridgeStatusControllerStartPollingForBridgeTxStatusAction | BridgeStatusControllerWipeBridgeStatusAction | BridgeStatusControllerResetStateAction | BridgeStatusControllerGetStateAction | BridgeStatusControllerSubmitTxAction | BridgeStatusControllerRestartPollingForFailedAttemptsAction | BridgeStatusControllerGetBridgeHistoryItemByTxMetaIdAction;
20+
export type BridgeStatusControllerStateChangeEvent = ControllerStateChangeEvent<typeof BRIDGE_STATUS_CONTROLLER_NAME, BridgeStatusControllerState>;
21+
-export type BridgeStatusControllerEvents = BridgeStatusControllerStateChangeEvent;
22+
+/**
23+
++ * This event is published when the destination bridge transaction is completed
24+
++ * The payload is the asset received on the destination chain
25+
++ */
26+
+export type BridgeStatusControllerDestinationTransactionCompletedEvent = {
27+
+ type: 'BridgeStatusController:destinationTransactionCompleted';
28+
+ payload: [CaipAssetType];
29+
+};
30+
+export type BridgeStatusControllerEvents = BridgeStatusControllerStateChangeEvent | BridgeStatusControllerDestinationTransactionCompletedEvent;
31+
/**
32+
* The external actions available to the BridgeStatusController.
33+
*/
134
diff --git a/dist/utils/swap-received-amount.cjs b/dist/utils/swap-received-amount.cjs
2-
index 4c01024e3ebba952b0bbded0aa7bb7e2690629f1..4809ec09ab2b94fc0dbb5160d464da4dab61c57e 100644
35+
index 4c01024e3ebba952b0bbded0aa7bb7e2690629f1..4c41504ce0609d8b7be1f6b045d0ac3e66447646 100644
336
--- a/dist/utils/swap-received-amount.cjs
437
+++ b/dist/utils/swap-received-amount.cjs
538
@@ -28,7 +28,7 @@ const getReceivedERC20Amount = (historyItem, txMeta) => {

app/components/UI/Predict/components/PredictMarketOutcome/PredictMarketOutcome.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,7 @@ const PredictMarketOutcome: React.FC<PredictMarketOutcomeProps> = ({
7373
if (isClosed && outcomeToken) {
7474
return outcomeToken.title;
7575
}
76-
return outcome.groupItemTitle;
77-
76+
return outcome.groupItemTitle;
7877
};
7978

8079
const getImageUrl = (): string => outcome.image;

app/components/UI/Tokens/index.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import { selectSortedTokenKeys } from '../../../selectors/tokenList';
3434
import { selectMultichainAccountsState2Enabled } from '../../../selectors/featureFlagController/multichainAccounts';
3535
import { selectSortedAssetsBySelectedAccountGroup } from '../../../selectors/assets/assets-list';
3636
import Loader from '../../../component-library/components-temp/Loader';
37+
import { selectSelectedInternalAccountByScope } from '../../../selectors/multichainAccounts/accounts';
38+
import { SolScope } from '@metamask/keyring-api';
3739

3840
interface TokenListNavigationParamList {
3941
AddAsset: { assetType: string };
@@ -61,6 +63,10 @@ const Tokens = memo(() => {
6163
const [refreshing, setRefreshing] = useState(false);
6264
const selectedAccountId = useSelector(selectSelectedInternalAccountId);
6365

66+
const selectedSolanaAccount =
67+
useSelector(selectSelectedInternalAccountByScope)(SolScope.Mainnet) || null;
68+
const isSolanaSelected = selectedSolanaAccount !== null;
69+
6470
const [showScamWarningModal, setShowScamWarningModal] = useState(false);
6571
const [isTokensLoading, setIsTokensLoading] = useState(true);
6672
const [renderedTokenKeys, setRenderedTokenKeys] = useState<
@@ -175,15 +181,15 @@ const Tokens = memo(() => {
175181
// Use InteractionManager for better performance during refresh
176182
InteractionManager.runAfterInteractions(() => {
177183
refreshTokens({
178-
isEvmSelected,
184+
isSolanaSelected,
179185
evmNetworkConfigurationsByChainId,
180186
nativeCurrencies,
181187
selectedAccountId,
182188
});
183189
setRefreshing(false);
184190
});
185191
}, [
186-
isEvmSelected,
192+
isSolanaSelected,
187193
evmNetworkConfigurationsByChainId,
188194
nativeCurrencies,
189195
selectedAccountId,

app/components/UI/Tokens/util/refreshTokens.test.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ jest.mock('../../../../util/Logger', () => ({
5757

5858
describe('refreshTokens', () => {
5959
const mockProps = {
60-
isEvmSelected: true,
60+
isSolanaSelected: true,
6161
evmNetworkConfigurationsByChainId: {
6262
'0x1': { chainId: '0x1' as Hex, nativeCurrency: 'ETH' },
6363
'0x89': { chainId: '0x89' as Hex, nativeCurrency: 'POL' },
@@ -100,24 +100,25 @@ describe('refreshTokens', () => {
100100
]);
101101
});
102102

103-
it('should not refresh tokens if EVM is not selected', async () => {
104-
await refreshTokens({ ...mockProps, isEvmSelected: false });
103+
it('should not refresh tokens if multichain network is not selected', async () => {
104+
await refreshTokens({ ...mockProps, isSolanaSelected: false });
105105

106106
// Ensure controllers are never called
107107
expect(
108108
Engine.context.TokenDetectionController.detectTokens,
109-
).not.toHaveBeenCalled();
109+
).toHaveBeenCalled();
110110
expect(
111111
Engine.context.TokenBalancesController.updateBalances,
112-
).not.toHaveBeenCalled();
113-
expect(
114-
Engine.context.AccountTrackerController.refresh,
115-
).not.toHaveBeenCalled();
112+
).toHaveBeenCalled();
113+
expect(Engine.context.AccountTrackerController.refresh).toHaveBeenCalled();
116114
expect(
117115
Engine.context.CurrencyRateController.updateExchangeRate,
118-
).not.toHaveBeenCalled();
116+
).toHaveBeenCalled();
119117
expect(
120118
Engine.context.TokenRatesController.updateExchangeRatesByChainId,
119+
).toHaveBeenCalled();
120+
expect(
121+
Engine.context.MultichainBalancesController.updateBalance,
121122
).not.toHaveBeenCalled();
122123
});
123124

@@ -134,9 +135,9 @@ describe('refreshTokens', () => {
134135
);
135136
});
136137

137-
it('should call updateBalance with selectedAccount ID when EVM is not selected', async () => {
138+
it('should call updateBalance with selectedAccount ID when Multichain network not selected', async () => {
138139
await refreshTokens({
139-
isEvmSelected: false,
140+
isSolanaSelected: true,
140141
evmNetworkConfigurationsByChainId: {},
141142
nativeCurrencies: [],
142143
selectedAccountId: 'test-account-id',
@@ -149,7 +150,7 @@ describe('refreshTokens', () => {
149150

150151
it('should not call updateBalance if selectedAccount is undefined', async () => {
151152
await refreshTokens({
152-
isEvmSelected: false,
153+
isSolanaSelected: false,
153154
evmNetworkConfigurationsByChainId: {},
154155
nativeCurrencies: [],
155156
selectedAccountId: undefined,

app/components/UI/Tokens/util/refreshTokens.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { InternalAccount } from '@metamask/keyring-internal-api';
55
import { performEvmRefresh } from './tokenRefreshUtils';
66

77
interface RefreshTokensProps {
8-
isEvmSelected: boolean;
8+
isSolanaSelected: boolean;
99
evmNetworkConfigurationsByChainId: Record<
1010
string,
1111
{ chainId: Hex; nativeCurrency: string }
@@ -15,12 +15,12 @@ interface RefreshTokensProps {
1515
}
1616

1717
export const refreshTokens = async ({
18-
isEvmSelected,
18+
isSolanaSelected,
1919
evmNetworkConfigurationsByChainId,
2020
nativeCurrencies,
2121
selectedAccountId,
2222
}: RefreshTokensProps) => {
23-
if (!isEvmSelected) {
23+
if (isSolanaSelected) {
2424
const { MultichainBalancesController } = Engine.context;
2525
if (selectedAccountId) {
2626
try {
@@ -29,8 +29,6 @@ export const refreshTokens = async ({
2929
Logger.error(error as Error, 'Error while refreshing NonEvm tokens');
3030
}
3131
}
32-
return;
3332
}
34-
3533
await performEvmRefresh(evmNetworkConfigurationsByChainId, nativeCurrencies);
3634
};

app/core/Engine/Engine.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,13 @@ import {
5858
} from './controllers/core-backend';
5959
import { AppStateWebSocketManager } from '../AppStateWebSocketManager';
6060
import { backupVault } from '../BackupVault';
61-
import { Hex, Json, KnownCaipNamespace } from '@metamask/utils';
61+
import {
62+
CaipAssetType,
63+
Hex,
64+
Json,
65+
KnownCaipNamespace,
66+
parseCaipAssetType,
67+
} from '@metamask/utils';
6268
import { providerErrors } from '@metamask/rpc-errors';
6369

6470
import {
@@ -614,6 +620,32 @@ export class Engine {
614620
},
615621
);
616622

623+
// Subscribe to destinationTransactionCompleted event from BridgeStatusController and refresh assets.
624+
this.controllerMessenger.subscribe(
625+
'BridgeStatusController:destinationTransactionCompleted',
626+
(caipAsset: CaipAssetType) => {
627+
try {
628+
const { chain } = parseCaipAssetType(caipAsset);
629+
630+
const { namespace: caipNamespace, reference } = chain;
631+
if (caipNamespace === 'eip155') {
632+
const hexChainId = toHex(reference);
633+
this.context.TokenDetectionController.detectTokens({
634+
chainIds: [hexChainId],
635+
});
636+
this.context.TokenBalancesController.updateBalances({
637+
chainIds: [hexChainId],
638+
});
639+
}
640+
} catch (error) {
641+
console.error(
642+
'Error handling BridgeStatusController:destinationTransactionCompleted event:',
643+
error,
644+
);
645+
}
646+
},
647+
);
648+
617649
this.appStateListener = AppState.addEventListener(
618650
'change',
619651
(state: AppStateStatus) => {

yarn.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7079,7 +7079,7 @@ __metadata:
70797079

70807080
"@metamask/bridge-status-controller@patch:@metamask/bridge-status-controller@npm%3A50.1.0#./.yarn/patches/@metamask-bridge-status-controller-npm-50.1.0-308ed9262e.patch::locator=metamask%40workspace%3A.":
70817081
version: 50.1.0
7082-
resolution: "@metamask/bridge-status-controller@patch:@metamask/bridge-status-controller@npm%3A50.1.0#./.yarn/patches/@metamask-bridge-status-controller-npm-50.1.0-308ed9262e.patch::version=50.1.0&hash=31ccdf&locator=metamask%40workspace%3A."
7082+
resolution: "@metamask/bridge-status-controller@patch:@metamask/bridge-status-controller@npm%3A50.1.0#./.yarn/patches/@metamask-bridge-status-controller-npm-50.1.0-308ed9262e.patch::version=50.1.0&hash=0c54ce&locator=metamask%40workspace%3A."
70837083
dependencies:
70847084
"@metamask/base-controller": "npm:^8.4.1"
70857085
"@metamask/controller-utils": "npm:^11.14.1"
@@ -7095,7 +7095,7 @@ __metadata:
70957095
"@metamask/network-controller": ^24.0.0
70967096
"@metamask/snaps-controllers": ^14.0.0
70977097
"@metamask/transaction-controller": ^60.0.0
7098-
checksum: 10/b2941dd08a35b7c32c5807beabc32a532bd30aead6c6b142d5117cceefca402b95a60619b5cc2b9f3a36ccc284d67d23657b8b94cfe0fe83978ec0d0368ea1cc
7098+
checksum: 10/8f93ea1ad53d270cf69689528491db3b1a7f42afa7f225210e975cbefe8a0d161ccfcd935a2f0bb01a362bd8c5e90593f356a445d2130f2586a233b82f031b40
70997099
languageName: node
71007100
linkType: hard
71017101

0 commit comments

Comments
 (0)