Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions .yarn/patches/react-native-npm-0.76.9-1c25352097.patch
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
diff --git a/Libraries/Pressability/Pressability.js b/Libraries/Pressability/Pressability.js
index 1bd6853b4202e4f726e80709660f6d049a4fc741..005008a26a5e07ae697098c82cce03dc86791635 100644
index 1bd6853b4202e4f726e80709660f6d049a4fc741..550651260129f3fb5081601a970775ec478de105 100644
--- a/Libraries/Pressability/Pressability.js
+++ b/Libraries/Pressability/Pressability.js
@@ -806,7 +806,7 @@ export default class Pressability {
@@ -806,7 +806,11 @@ export default class Pressability {
if (typeof this._responderID === 'number') {
UIManager.measure(this._responderID, this._measureCallback);
} else {
- this._responderID.measure(this._measureCallback);
+ this._responderID.measureAsyncOnUI(this._measureCallback);
+ if (Platform.OS === "android") {
+ this._responderID.measureAsyncOnUI(this._measureCallback);
+ } else {
+ this._responderID.measure(this._measureCallback);
+ }
}
}

diff --git a/Libraries/ReactNative/FabricUIManager.js b/Libraries/ReactNative/FabricUIManager.js
index a31fe41f232a4b5e79f37c2a98f51ee8208c9585..d77b10e87657987092e92fcd2ecbe9b368eb03eb 100644
--- a/Libraries/ReactNative/FabricUIManager.js
Expand Down Expand Up @@ -97,7 +101,7 @@ index ec4354583ac04fa8df6de7c1896902a4c7d5c4b4..d372b05e17749b73e94bc51616b9123c
+ UIView<RCTComponentViewProtocol> *view = [self->_componentViewRegistry findComponentViewWithTag:reactTag];
+ if (!view) {
+ // this view was probably collapsed out
+ RCTLogWarn(@"measure cannot find view with tag #%@", reactTag);
+ RCTLogWarn(@"measure cannot find view with tag #%@", @(reactTag));
+ callbackCopy({});
+ return;
+ }
Expand Down
111 changes: 111 additions & 0 deletions app/components/UI/Bridge/Views/BridgeView/BridgeView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { MOCK_ENTROPY_SOURCE as mockEntropySource } from '../../../../../util/te
import { RootState } from '../../../../../reducers';
import { mockQuoteWithMetadata } from '../../_mocks_/bridgeQuoteWithMetadata';
import { BridgeViewMode } from '../../types';
import { useGasIncluded } from '../../hooks/useGasIncluded';
import { useIsSendBundleSupported } from '../../hooks/useIsSendBundleSupported';

// Mock the account-tree-controller file that imports the problematic module
jest.mock(
Expand Down Expand Up @@ -255,6 +257,16 @@ jest.mock('../../hooks/useBridgeQuoteData', () => ({
.mockImplementation(() => mockUseBridgeQuoteData),
}));

// Mock useGasIncluded hook
jest.mock('../../hooks/useGasIncluded', () => ({
useGasIncluded: jest.fn(),
}));

// Mock useIsSendBundleSupported hook (dependency of useGasIncluded)
jest.mock('../../hooks/useIsSendBundleSupported', () => ({
useIsSendBundleSupported: jest.fn().mockReturnValue(false),
}));

jest.mock('../../../../../util/address', () => ({
...jest.requireActual('../../../../../util/address'),
isHardwareAccount: jest.fn(),
Expand All @@ -266,6 +278,8 @@ describe('BridgeView', () => {
beforeEach(() => {
jest.clearAllMocks();
// Set default mock values
(useGasIncluded as jest.Mock).mockReturnValue(undefined);
(useIsSendBundleSupported as jest.Mock).mockReturnValue(false);
});

it('renders', async () => {
Expand Down Expand Up @@ -1578,4 +1592,101 @@ describe('BridgeView', () => {
expect(getByText('ETH')).toBeTruthy();
});
});

describe('gasIncluded functionality', () => {
it('calls useGasIncluded hook with source chain ID', () => {
const testState = createBridgeTestState({
bridgeReducerOverrides: {
sourceToken: {
address: '0x0000000000000000000000000000000000000000',
chainId: '0x1',
decimals: 18,
symbol: 'ETH',
},
},
});

renderScreen(
BridgeView,
{ name: Routes.BRIDGE.ROOT },
{ state: testState },
);

expect(useGasIncluded).toHaveBeenCalledWith('0x1');
});

it('calls useGasIncluded with undefined when source token not set', () => {
const testState = createBridgeTestState({
bridgeReducerOverrides: {
sourceToken: undefined,
},
});

renderScreen(
BridgeView,
{ name: Routes.BRIDGE.ROOT },
{ state: testState },
);

expect(useGasIncluded).toHaveBeenCalledWith(undefined);
});

it('updates gasIncluded when source chain changes', () => {
const testState = createBridgeTestState({
bridgeReducerOverrides: {
sourceToken: {
address: '0x0000000000000000000000000000000000000000',
chainId: '0x1',
decimals: 18,
symbol: 'ETH',
},
},
});

const { store } = renderScreen(
BridgeView,
{ name: Routes.BRIDGE.ROOT },
{ state: testState },
);

// Change source token to different chain
act(() => {
store.dispatch(
setSourceToken({
address: '0x0000000000000000000000000000000000000000',
chainId: '0xa', // Optimism
decimals: 18,
symbol: 'ETH',
}),
);
});

// useGasIncluded should be called with the new chain ID
expect(useGasIncluded).toHaveBeenCalledWith('0xa');
});

it('calls useGasIncluded with non-EVM chainId directly', () => {
const testState = createBridgeTestState({
bridgeReducerOverrides: {
sourceToken: {
address: 'SomeNonEvmAddress',
chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
decimals: 9,
symbol: 'SOL',
},
},
});

renderScreen(
BridgeView,
{ name: Routes.BRIDGE.ROOT },
{ state: testState },
);

// Hook receives the raw chainId and handles filtering internally
expect(useGasIncluded).toHaveBeenCalledWith(
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
);
});
});
});
4 changes: 4 additions & 0 deletions app/components/UI/Bridge/Views/BridgeView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import { RootState } from '../../../../../reducers/index.ts';
import { BRIDGE_MM_FEE_RATE } from '@metamask/bridge-controller';
import { isNullOrUndefined } from '@metamask/utils';
import { useBridgeQuoteEvents } from '../../hooks/useBridgeQuoteEvents/index.ts';
import { useGasIncluded } from '../../hooks/useGasIncluded';

export interface BridgeRouteParams {
sourcePage: string;
Expand Down Expand Up @@ -141,6 +142,9 @@ const BridgeView = () => {

const updateQuoteParams = useBridgeQuoteRequest();

// Update gasIncluded state based on source chain capabilities
useGasIncluded(sourceToken?.chainId);

const initialSourceToken = route.params?.sourceToken;
const initialSourceAmount = route.params?.sourceAmount;
const initialDestToken = route.params?.destToken;
Expand Down
1 change: 1 addition & 0 deletions app/components/UI/Bridge/_mocks_/bridgeReducerState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const mockBridgeReducerState: BridgeState = {
selectedDestChainId: '0xa',
slippage: '0.5',
isSubmittingTx: false,
gasIncluded: false,
bridgeViewMode: BridgeViewMode.Bridge,
isSelectingRecipient: false,
};
13 changes: 6 additions & 7 deletions app/components/UI/Bridge/hooks/useBridgeQuoteRequest/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import {
selectSelectedDestChainId,
selectSlippage,
selectDestAddress,
selectGasIncluded,
} from '../../../../../core/redux/slices/bridge';
import { getDecimalChainId } from '../../../../../util/networks';
import { calcTokenValue } from '../../../../../util/transactions';
import { debounce } from 'lodash';
import { useUnifiedSwapBridgeContext } from '../useUnifiedSwapBridgeContext';
import { selectShouldUseSmartTransaction } from '../../../../../selectors/smartTransactionsController';
import { selectSourceWalletAddress } from '../../../../../selectors/bridge';
import useIsInsufficientBalance from '../useInsufficientBalance';
import { useLatestBalance } from '../useLatestBalance';
Expand All @@ -34,9 +34,6 @@ export const useBridgeQuoteRequest = () => {
const walletAddress = useSelector(selectSourceWalletAddress);
const destAddress = useSelector(selectDestAddress);
const context = useUnifiedSwapBridgeContext();
const shouldUseSmartTransaction = useSelector(
selectShouldUseSmartTransaction,
);

const latestSourceBalance = useLatestBalance({
address: sourceToken?.address,
Expand All @@ -55,6 +52,8 @@ export const useBridgeQuoteRequest = () => {
insufficientBalRef.current = insufficientBal;
}, [insufficientBal]);

const gasIncluded = useSelector(selectGasIncluded);

/**
* Updates quote parameters in the bridge controller
*/
Expand Down Expand Up @@ -86,8 +85,8 @@ export const useBridgeQuoteRequest = () => {
slippage: slippage ? Number(slippage) : undefined,
walletAddress,
destWalletAddress: destAddress ?? walletAddress,
gasIncluded: shouldUseSmartTransaction,
gasIncluded7702: false, // TODO research how to handle this
gasIncluded,
gasIncluded7702: false, // TODO: https://consensyssoftware.atlassian.net/browse/STX-263
insufficientBal: insufficientBalRef.current,
};

Expand All @@ -104,7 +103,7 @@ export const useBridgeQuoteRequest = () => {
walletAddress,
destAddress,
context,
shouldUseSmartTransaction,
gasIncluded,
]);

// Create a stable debounced function that persists across renders
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,4 +345,74 @@ describe('useBridgeQuoteRequest', () => {
// Reset mock
(isSolanaChainId as jest.Mock).mockReset();
});

describe('gasIncluded parameter', () => {
it('includes gasIncluded true in quote request when enabled', async () => {
const testState = createBridgeTestState({
bridgeReducerOverrides: {
gasIncluded: true,
},
});

const { result } = renderHookWithProvider(() => useBridgeQuoteRequest(), {
state: testState,
});

await act(async () => {
await result.current();
jest.advanceTimersByTime(DEBOUNCE_WAIT);
});

expect(spyUpdateBridgeQuoteRequestParams).toHaveBeenCalledWith(
expect.objectContaining({
gasIncluded: true,
}),
undefined,
);
});

it('includes gasIncluded false in quote request when disabled', async () => {
const testState = createBridgeTestState({
bridgeReducerOverrides: {
gasIncluded: false,
},
});

const { result } = renderHookWithProvider(() => useBridgeQuoteRequest(), {
state: testState,
});

await act(async () => {
await result.current();
jest.advanceTimersByTime(DEBOUNCE_WAIT);
});

expect(spyUpdateBridgeQuoteRequestParams).toHaveBeenCalledWith(
expect.objectContaining({
gasIncluded: false,
}),
undefined,
);
});

it('includes gasIncluded7702 false in quote request', async () => {
const testState = createBridgeTestState();

const { result } = renderHookWithProvider(() => useBridgeQuoteRequest(), {
state: testState,
});

await act(async () => {
await result.current();
jest.advanceTimersByTime(DEBOUNCE_WAIT);
});

expect(spyUpdateBridgeQuoteRequestParams).toHaveBeenCalledWith(
expect.objectContaining({
gasIncluded7702: false,
}),
undefined,
);
});
});
});
46 changes: 46 additions & 0 deletions app/components/UI/Bridge/hooks/useGasIncluded/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { setGasIncluded } from '../../../../../core/redux/slices/bridge';
import { selectShouldUseSmartTransaction } from '../../../../../selectors/smartTransactionsController';
import { useIsSendBundleSupported } from '../useIsSendBundleSupported';
import { RootState } from '../../../../../reducers';
import { CaipChainId, Hex } from '@metamask/utils';
import {
formatChainIdToHex,
isNonEvmChainId,
} from '@metamask/bridge-controller';

/**
* Hook that calculates and updates gasIncluded state for bridge and swap transactions.
* Should be used at the page level (e.g., BridgeView) to avoid repeated calculations.
*
* Returns true only when BOTH smart transactions are enabled AND sendBundle is supported.
* Only applies to EVM chains - non-EVM chains will always have gasIncluded set to false.
*
* @param chainId - The chain ID to check gasIncluded support for (can be Hex, CAIP, or other format)
*/
export const useGasIncluded = (chainId?: Hex | CaipChainId | string) => {
const dispatch = useDispatch();

// Only check gasIncluded for EVM chains
const evmChainId = useMemo(() => {
if (!chainId || isNonEvmChainId(chainId)) {
return undefined;
}
return formatChainIdToHex(chainId);
}, [chainId]);

const isSendBundleSupportedForChain = useIsSendBundleSupported(evmChainId);

const shouldUseSmartTransaction = useSelector((state: RootState) =>
selectShouldUseSmartTransaction(state, evmChainId),
);

// gasIncluded is true only when BOTH smart transactions are enabled AND sendBundle is supported
const gasIncluded =
shouldUseSmartTransaction && Boolean(isSendBundleSupportedForChain);

useEffect(() => {
dispatch(setGasIncluded(gasIncluded));
}, [gasIncluded, dispatch]);
};
Loading
Loading