Skip to content

Commit b84864b

Browse files
authored
fix(bridge): normalize Polygon native token addresses cp-7.75.0 (#29440)
<!-- 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** <!-- 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? --> Polygon native POL can enter the bridge/swaps flow from token details with Polygon's native token alias address (`0x0000000000000000000000000000000000001010`). Bridge state expects native EVM assets to use the swaps native zero address, so the alias can break downstream balance, gas, and quote matching behavior. This change normalizes source and destination bridge tokens as they are stored in the bridge Redux slice. That keeps bridge state canonical for all callers of `setSourceToken` and `setDestToken`, including the POL token details swap entry point. ## **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: Fixed a bug that prevented quotes when swapping from POL token details ## **Related issues** Issue: #29231 ## **Manual testing steps** ```gherkin Feature: POL swap quotes from token details Scenario: User starts a swap from the POL asset details screen Given the user has POL on Polygon Mainnet And the user opens POL from the wallet token list When the user taps Swap from the POL token details screen And the user enters a valid POL amount Then the swap flow fetches and displays available quotes ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** N/A ### **After** N/A ## **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) - [x] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [x] 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 - [x] 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 is ambiguous, a checked 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. <!-- Generated with the help of the pr-description AI skill -->
1 parent 62ed9c6 commit b84864b

2 files changed

Lines changed: 46 additions & 3 deletions

File tree

app/core/redux/slices/bridge/index.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import reducer, {
88
setSlippage,
99
setBridgeViewMode,
1010
selectBridgeViewMode,
11+
setSourceToken,
1112
setDestToken,
1213
setIsDestTokenManuallySet,
1314
selectIsDestTokenManuallySet,
@@ -52,6 +53,15 @@ describe('bridge slice', () => {
5253
balanceFiat: '100',
5354
};
5455

56+
const polygonNativeToken: BridgeToken = {
57+
address: '0x0000000000000000000000000000000000001010',
58+
symbol: 'POL',
59+
decimals: 18,
60+
image: '',
61+
chainId: '0x89' as Hex,
62+
name: 'POL',
63+
};
64+
5565
describe('initial state', () => {
5666
it('has correct initial state', () => {
5767
expect(initialState).toEqual({
@@ -185,6 +195,14 @@ describe('bridge slice', () => {
185195
});
186196

187197
describe('setDestToken', () => {
198+
it('normalizes Polygon native token address', () => {
199+
const state = reducer(initialState, setDestToken(polygonNativeToken));
200+
201+
expect(state.destToken?.address).toBe(
202+
'0x0000000000000000000000000000000000000000',
203+
);
204+
});
205+
188206
it('sets the destination token and updates selectedDestChainId', () => {
189207
const action = setDestToken(mockDestToken);
190208
const state = reducer(initialState, action);
@@ -206,6 +224,16 @@ describe('bridge slice', () => {
206224
});
207225
});
208226

227+
describe('setSourceToken', () => {
228+
it('normalizes Polygon native token address', () => {
229+
const state = reducer(initialState, setSourceToken(polygonNativeToken));
230+
231+
expect(state.sourceToken?.address).toBe(
232+
'0x0000000000000000000000000000000000000000',
233+
);
234+
});
235+
});
236+
209237
describe('setIsDestTokenManuallySet', () => {
210238
it('sets the flag to true', () => {
211239
const action = setIsDestTokenManuallySet(true);

app/core/redux/slices/bridge/index.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { selectCanSignTransactions } from '../../../../selectors/accountsControl
3636
import { selectBasicFunctionalityEnabled } from '../../../../selectors/settings';
3737
import { hasMinimumRequiredVersion } from './utils/hasMinimumRequiredVersion';
3838
import { Bip44TokensForDefaultPairs } from '../../../../components/UI/Bridge/constants/default-swap-dest-tokens';
39+
import { normalizeTokenAddress } from '../../../../components/UI/Bridge/utils/tokenUtils';
3940

4041
export const selectBridgeControllerState = (state: RootState) =>
4142
state.engine.backgroundState?.BridgeController;
@@ -108,6 +109,19 @@ export const initialState: BridgeState = {
108109

109110
const name = 'bridge';
110111

112+
const normalizeBridgeToken = <T extends BridgeToken | undefined>(
113+
token: T,
114+
): T => {
115+
if (!token) {
116+
return token;
117+
}
118+
119+
const normalizedAddress = normalizeTokenAddress(token.address, token.chainId);
120+
return normalizedAddress === token.address
121+
? token
122+
: ({ ...token, address: normalizedAddress } as T);
123+
};
124+
111125
export const setSourceTokenExchangeRate = createAsyncThunk(
112126
'bridge/setSourceTokenExchangeRate',
113127
getTokenExchangeRate,
@@ -156,12 +170,13 @@ const slice = createSlice({
156170
...initialState,
157171
}),
158172
setSourceToken: (state, action: PayloadAction<BridgeToken | undefined>) => {
159-
state.sourceToken = action.payload;
173+
state.sourceToken = normalizeBridgeToken(action.payload);
160174
},
161175
setDestToken: (state, action: PayloadAction<BridgeToken>) => {
162-
state.destToken = action.payload;
176+
const destToken = normalizeBridgeToken(action.payload);
177+
state.destToken = destToken;
163178
// Update selectedDestChainId to match the destination token's chain ID
164-
state.selectedDestChainId = action.payload.chainId;
179+
state.selectedDestChainId = destToken.chainId;
165180
},
166181
/**
167182
* Sets whether the destination token was manually selected by the user.

0 commit comments

Comments
 (0)