Skip to content

Commit c92ab47

Browse files
Merge branch 'release/7.64.1' into release/7.64.1-Changelog
2 parents 204aab3 + 2205012 commit c92ab47

File tree

6 files changed

+218
-40
lines changed

6 files changed

+218
-40
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
### Added
1515

16+
- Change CardHome button colors (#25737)
1617
- Added one-click trading for Perps, allowing users to deposit funds and execute trades seamlessly within the order view (#24964)
1718
- Update slippage UI, adding option for users to set a custom slippage (#25124)
1819
- Updated stablecoin lending cta to be right-aligned and not render the percentage (#25351)

android/app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ android {
188188
minSdkVersion rootProject.ext.minSdkVersion
189189
targetSdkVersion rootProject.ext.targetSdkVersion
190190
versionName "7.64.1"
191-
versionCode 3646
191+
versionCode 3667
192192
testBuildType System.getProperty('testBuildType', 'debug')
193193
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
194194
manifestPlaceholders.MM_BRANCH_KEY_TEST = "$System.env.MM_BRANCH_KEY_TEST"

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

Lines changed: 175 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import reducer, {
1717
selectIsBridgeEnabledDest,
1818
selectIsSwapsLive,
1919
selectDestChainRanking,
20+
selectSourceChainRanking,
2021
} from '.';
2122
import {
2223
BridgeToken,
@@ -531,25 +532,28 @@ describe('bridge slice', () => {
531532
});
532533

533534
it('returns false when bridge is not enabled as source for the chain', () => {
534-
const mockState = cloneDeep(mockRootState) as unknown as RootState;
535-
// @ts-expect-error - Mock state has correct structure at runtime
536-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
537-
mockState.engine.backgroundState.RemoteFeatureFlagController.remoteFeatureFlags.bridgeConfigV2!.chains[
538-
'eip155:1'
539-
].isActiveSrc = false;
535+
const mockState = cloneDeep(mockRootState);
536+
// Remove chain from chainRanking to disable it (chainRanking presence = enabled)
537+
mockState.engine.backgroundState.RemoteFeatureFlagController.remoteFeatureFlags.bridgeConfigV2.chainRanking =
538+
mockState.engine.backgroundState.RemoteFeatureFlagController.remoteFeatureFlags.bridgeConfigV2.chainRanking.filter(
539+
(chain) => chain.chainId !== 'eip155:1',
540+
);
540541

541-
const result = selectIsBridgeEnabledSource(mockState, '0x1');
542+
const result = selectIsBridgeEnabledSource(
543+
mockState as unknown as RootState,
544+
'0x1',
545+
);
542546

543547
expect(result).toBe(false);
544548
});
545549

546-
it('returns undefined when chain is not in bridge config', () => {
550+
it('returns false when chain is not in bridge config', () => {
547551
const result = selectIsBridgeEnabledSource(
548552
mockRootState as unknown as RootState,
549553
'0x999' as Hex,
550554
);
551555

552-
expect(result).toBeUndefined();
556+
expect(result).toBe(false);
553557
});
554558
});
555559

@@ -597,6 +601,77 @@ describe('bridge slice', () => {
597601
});
598602
});
599603

604+
describe('selectSourceChainRanking', () => {
605+
it('returns only supported and user-configured chains', () => {
606+
const result = selectSourceChainRanking(
607+
mockRootState as unknown as RootState,
608+
);
609+
610+
// Should return chains that are both in ALLOWED_BRIDGE_CHAIN_IDS
611+
// and in the user's configured networks
612+
expect(Array.isArray(result)).toBe(true);
613+
expect(result.length).toBeGreaterThan(0);
614+
615+
// Ethereum (0x1) is allowed and configured in the mock state
616+
expect(result.some((chain) => chain.chainId === 'eip155:1')).toBe(true);
617+
});
618+
619+
it('filters out unsupported EVM chains from chainRanking', () => {
620+
const mockState = cloneDeep(mockRootState);
621+
mockState.engine.backgroundState.RemoteFeatureFlagController.remoteFeatureFlags.bridgeConfigV2.chainRanking =
622+
[
623+
...mockState.engine.backgroundState.RemoteFeatureFlagController
624+
.remoteFeatureFlags.bridgeConfigV2.chainRanking,
625+
{ chainId: 'eip155:99999', name: 'Unsupported EVM Chain' },
626+
];
627+
628+
const result = selectSourceChainRanking(
629+
mockState as unknown as RootState,
630+
);
631+
632+
expect(result.some((chain) => chain.chainId === 'eip155:99999')).toBe(
633+
false,
634+
);
635+
});
636+
637+
it('filters out unsupported non-EVM chains from chainRanking', () => {
638+
const mockState = cloneDeep(mockRootState);
639+
mockState.engine.backgroundState.RemoteFeatureFlagController.remoteFeatureFlags.bridgeConfigV2.chainRanking =
640+
[
641+
...mockState.engine.backgroundState.RemoteFeatureFlagController
642+
.remoteFeatureFlags.bridgeConfigV2.chainRanking,
643+
{ chainId: 'cosmos:cosmoshub-4', name: 'Unsupported Cosmos Chain' },
644+
];
645+
646+
const result = selectSourceChainRanking(
647+
mockState as unknown as RootState,
648+
);
649+
650+
expect(
651+
result.some((chain) => chain.chainId === 'cosmos:cosmoshub-4'),
652+
).toBe(false);
653+
});
654+
655+
it('filters out chains not in user-configured networks', () => {
656+
const result = selectSourceChainRanking(
657+
mockRootState as unknown as RootState,
658+
);
659+
660+
// Optimism (0xa) is in chainRanking and ALLOWED_BRIDGE_CHAIN_IDS
661+
// AND in the mock user's configured networks
662+
const hasOptimism = result.some((chain) => chain.chainId === 'eip155:10');
663+
expect(hasOptimism).toBe(true);
664+
665+
// Verify no chains appear that aren't in the user's configured networks
666+
// The user only has Ethereum (0x1) and Optimism (0xa) configured as EVM networks
667+
result.forEach((chain) => {
668+
if (chain.chainId.startsWith('eip155:')) {
669+
expect(['eip155:1', 'eip155:10']).toContain(chain.chainId);
670+
}
671+
});
672+
});
673+
});
674+
600675
describe('selectDestChainRanking', () => {
601676
it('returns chainRanking from feature flags', () => {
602677
const result = selectDestChainRanking(
@@ -622,16 +697,75 @@ describe('bridge slice', () => {
622697
expect(hasEthereum).toBe(true);
623698
});
624699

625-
it('returns all chains without filtering (unlike selectSourceChainRanking)', () => {
700+
it('returns all supported chains without filtering by user-configured networks', () => {
626701
const result = selectDestChainRanking(
627702
mockRootState as unknown as RootState,
628703
);
629704

630-
// selectDestChainRanking should return all chains from feature flags
705+
// selectDestChainRanking should return all supported chains from feature flags
631706
// This is the key difference from selectSourceChainRanking which filters
632707
// by user-configured networks
633708
expect(result.length).toBeGreaterThan(0);
634709
});
710+
711+
it('filters out unsupported EVM chains not in ALLOWED_BRIDGE_CHAIN_IDS', () => {
712+
const mockState = cloneDeep(mockRootState);
713+
mockState.engine.backgroundState.RemoteFeatureFlagController.remoteFeatureFlags.bridgeConfigV2.chainRanking =
714+
[
715+
...mockState.engine.backgroundState.RemoteFeatureFlagController
716+
.remoteFeatureFlags.bridgeConfigV2.chainRanking,
717+
{ chainId: 'eip155:99999', name: 'Unsupported Future Chain' },
718+
];
719+
720+
const result = selectDestChainRanking(mockState as unknown as RootState);
721+
722+
expect(result.some((chain) => chain.chainId === 'eip155:99999')).toBe(
723+
false,
724+
);
725+
expect(result.some((chain) => chain.chainId === 'eip155:1')).toBe(true);
726+
});
727+
728+
it('filters out unsupported non-EVM chains not in ALLOWED_BRIDGE_CHAIN_IDS', () => {
729+
const mockState = cloneDeep(mockRootState);
730+
mockState.engine.backgroundState.RemoteFeatureFlagController.remoteFeatureFlags.bridgeConfigV2.chainRanking =
731+
[
732+
...mockState.engine.backgroundState.RemoteFeatureFlagController
733+
.remoteFeatureFlags.bridgeConfigV2.chainRanking,
734+
{ chainId: 'cosmos:cosmoshub-4', name: 'Unsupported Cosmos Chain' },
735+
];
736+
737+
const result = selectDestChainRanking(mockState as unknown as RootState);
738+
739+
expect(
740+
result.some((chain) => chain.chainId === 'cosmos:cosmoshub-4'),
741+
).toBe(false);
742+
expect(
743+
result.some(
744+
(chain) =>
745+
chain.chainId === 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
746+
),
747+
).toBe(true);
748+
});
749+
});
750+
751+
describe('selectIsBridgeEnabledSource - ALLOWED_BRIDGE_CHAIN_IDS filtering', () => {
752+
it('returns false for a chain in chainRanking but not in ALLOWED_BRIDGE_CHAIN_IDS', () => {
753+
const mockState = cloneDeep(mockRootState);
754+
// Add an unsupported chain to chainRanking
755+
mockState.engine.backgroundState.RemoteFeatureFlagController.remoteFeatureFlags.bridgeConfigV2.chainRanking =
756+
[
757+
...mockState.engine.backgroundState.RemoteFeatureFlagController
758+
.remoteFeatureFlags.bridgeConfigV2.chainRanking,
759+
{ chainId: 'eip155:99999', name: 'Unsupported Future Chain' },
760+
];
761+
762+
const result = selectIsBridgeEnabledSource(
763+
mockState as unknown as RootState,
764+
'0x1869F' as Hex, // hex for 99999
765+
);
766+
767+
expect(result).toBe(false);
768+
});
635769
});
636770

637771
describe('selectIsSwapsLive', () => {
@@ -671,19 +805,24 @@ describe('bridge slice', () => {
671805
});
672806

673807
it('returns false when bridge is disabled for both source and destination', () => {
674-
const mockState = cloneDeep(mockRootState) as unknown as RootState;
675-
// @ts-expect-error - Mock state has correct structure at runtime
676-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
677-
mockState.engine.backgroundState.RemoteFeatureFlagController.remoteFeatureFlags.bridgeConfigV2!.chains[
678-
'eip155:1'
679-
].isActiveSrc = false;
680-
// @ts-expect-error - Mock state has correct structure at runtime
808+
const mockState = cloneDeep(mockRootState);
809+
// Remove chain from chainRanking to disable source (chainRanking presence = enabled)
810+
mockState.engine.backgroundState.RemoteFeatureFlagController.remoteFeatureFlags.bridgeConfigV2.chainRanking =
811+
mockState.engine.backgroundState.RemoteFeatureFlagController.remoteFeatureFlags.bridgeConfigV2.chainRanking.filter(
812+
(chain) => chain.chainId !== 'eip155:1',
813+
);
814+
// Disable destination via chains config
681815
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
682-
mockState.engine.backgroundState.RemoteFeatureFlagController.remoteFeatureFlags.bridgeConfigV2!.chains[
816+
(
817+
mockState as any
818+
).engine.backgroundState.RemoteFeatureFlagController.remoteFeatureFlags.bridgeConfigV2!.chains[
683819
'eip155:1'
684820
].isActiveDest = false;
685821

686-
const result = selectIsSwapsLive(mockState, '0x1');
822+
const result = selectIsSwapsLive(
823+
mockState as unknown as RootState,
824+
'0x1',
825+
);
687826

688827
expect(result).toBe(false);
689828
});
@@ -697,13 +836,24 @@ describe('bridge slice', () => {
697836
expect(result).toBeUndefined();
698837
});
699838

700-
it('returns false when support flag is disabled', () => {
701-
const mockState = cloneDeep(mockRootState) as unknown as RootState;
702-
// @ts-expect-error - Mock state has correct structure at runtime
839+
it('returns false when support flag is disabled and source is not in chainRanking', () => {
840+
const mockState = cloneDeep(mockRootState);
841+
// Remove chain from chainRanking to disable source
842+
mockState.engine.backgroundState.RemoteFeatureFlagController.remoteFeatureFlags.bridgeConfigV2.chainRanking =
843+
mockState.engine.backgroundState.RemoteFeatureFlagController.remoteFeatureFlags.bridgeConfigV2.chainRanking.filter(
844+
(chain) => chain.chainId !== 'eip155:1',
845+
);
846+
// Disable destination via support flag
703847
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
704-
mockState.engine.backgroundState.RemoteFeatureFlagController.remoteFeatureFlags.bridgeConfigV2!.support = false;
848+
(
849+
mockState as any
850+
).engine.backgroundState.RemoteFeatureFlagController.remoteFeatureFlags.bridgeConfigV2!.support =
851+
false;
705852

706-
const result = selectIsSwapsLive(mockState, '0x1');
853+
const result = selectIsSwapsLive(
854+
mockState as unknown as RootState,
855+
'0x1',
856+
);
707857

708858
expect(result).toBe(false);
709859
});

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

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,26 @@ export const selectBridgeFeatureFlags = createSelector(
279279
);
280280

281281
/**
282-
* Selector that returns the chainRanking from feature flags filtered by user-configured networks.
282+
* Checks whether a CAIP chain ID from chainRanking is supported by this version of the client.
283+
* This ensures that chains added to the remote chainRanking flag in the future
284+
* won't be surfaced by older app versions that lack support for them.
285+
*/
286+
const isAllowedBridgeChainId = (caipChainId: string): boolean => {
287+
if (caipChainId.startsWith('eip155:')) {
288+
const hexChainId = formatChainIdToHex(caipChainId);
289+
return ALLOWED_BRIDGE_CHAIN_IDS.includes(
290+
hexChainId as AllowedBridgeChainIds,
291+
);
292+
}
293+
return ALLOWED_BRIDGE_CHAIN_IDS.includes(
294+
caipChainId as AllowedBridgeChainIds,
295+
);
296+
};
297+
298+
/**
299+
* Selector that returns the chainRanking from feature flags filtered by:
300+
* 1. Chains supported by this version of the client
301+
* 2. User-configured networks
283302
* Used by NetworkPills in SOURCE mode to show all networks the user has added.
284303
*/
285304
export const selectSourceChainRanking = createSelector(
@@ -297,6 +316,11 @@ export const selectSourceChainRanking = createSelector(
297316
return chainRanking.filter((chain) => {
298317
const { chainId } = chain;
299318

319+
// First, ensure this chain is supported by the current client version
320+
if (!isAllowedBridgeChainId(chainId)) {
321+
return false;
322+
}
323+
300324
// For EVM chains (eip155:*), extract the hex chain ID and check if enabled
301325
if (chainId.startsWith('eip155:')) {
302326
const hexChainId = formatChainIdToHex(chainId);
@@ -310,14 +334,17 @@ export const selectSourceChainRanking = createSelector(
310334
);
311335

312336
/**
313-
* Selector that returns all chains from chainRanking (all bridge-supported networks).
337+
* Selector that returns all chains from chainRanking that are supported by this
338+
* version of the client.
314339
* Used by NetworkPills in DEST mode to show all available destination networks.
315340
*/
316341
export const selectDestChainRanking = createSelector(
317342
selectBridgeFeatureFlags,
318343
(bridgeFeatureFlags) => {
319344
const { chainRanking } = bridgeFeatureFlags;
320-
return chainRanking ?? [];
345+
return (chainRanking ?? []).filter((chain) =>
346+
isAllowedBridgeChainId(chain.chainId),
347+
);
321348
},
322349
);
323350

@@ -333,9 +360,9 @@ export const selectIsBridgeEnabledSourceFactory = createSelector(
333360
(bridgeFeatureFlags) => (chainId: Hex | CaipChainId) => {
334361
const caipChainId = formatChainIdToCaip(chainId);
335362

336-
return (
337-
bridgeFeatureFlags.support &&
338-
bridgeFeatureFlags.chains[caipChainId]?.isActiveSrc
363+
return bridgeFeatureFlags.chainRanking?.some(
364+
(chain) =>
365+
chain.chainId === caipChainId && isAllowedBridgeChainId(chain.chainId),
339366
);
340367
},
341368
);

bitrise.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3472,13 +3472,13 @@ app:
34723472
VERSION_NAME: 7.64.1
34733473
- opts:
34743474
is_expand: false
3475-
VERSION_NUMBER: 3646
3475+
VERSION_NUMBER: 3667
34763476
- opts:
34773477
is_expand: false
34783478
FLASK_VERSION_NAME: 7.64.1
34793479
- opts:
34803480
is_expand: false
3481-
FLASK_VERSION_NUMBER: 3646
3481+
FLASK_VERSION_NUMBER: 3667
34823482
- opts:
34833483
is_expand: false
34843484
ANDROID_APK_LINK: ''

0 commit comments

Comments
 (0)