Skip to content

Commit a38604e

Browse files
fix: Gas station improvements
1 parent b0b3b88 commit a38604e

File tree

3 files changed

+143
-29
lines changed

3 files changed

+143
-29
lines changed

ui/pages/confirmations/components/confirm/info/shared/gas-fee-token-icon/gas-fee-token-icon.tsx

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
1-
import React from 'react';
2-
import { Hex } from '@metamask/utils';
1+
import { AvatarAccountSize } from '@metamask/design-system-react';
32
import { TransactionMeta } from '@metamask/transaction-controller';
3+
import { Hex } from '@metamask/utils';
4+
import React from 'react';
45
import { useSelector } from 'react-redux';
5-
6-
import { NATIVE_TOKEN_ADDRESS } from '../../../../../../../../shared/constants/transaction';
7-
import { useConfirmContext } from '../../../../../context/confirm';
8-
import { selectNetworkConfigurationByChainId } from '../../../../../../../selectors';
9-
import Identicon from '../../../../../../../components/ui/identicon';
106
import { CHAIN_ID_TOKEN_IMAGE_MAP } from '../../../../../../../../shared/constants/network';
7+
import { NATIVE_TOKEN_ADDRESS } from '../../../../../../../../shared/constants/transaction';
8+
import { PreferredAvatar } from '../../../../../../../components/app/preferred-avatar';
119
import {
1210
AvatarToken,
1311
AvatarTokenSize,
1412
Box,
1513
} from '../../../../../../../components/component-library';
14+
import Identicon from '../../../../../../../components/ui/identicon';
1615
import { BackgroundColor } from '../../../../../../../helpers/constants/design-system';
16+
import {
17+
selectERC20TokensByChain,
18+
selectNetworkConfigurationByChainId,
19+
} from '../../../../../../../selectors';
20+
import { useConfirmContext } from '../../../../../context/confirm';
1721

1822
export enum GasFeeTokenIconSize {
1923
Sm = 'sm',
@@ -36,13 +40,30 @@ export function GasFeeTokenIcon({
3640
selectNetworkConfigurationByChainId(state, chainId),
3741
);
3842

43+
const erc20TokensByChain = useSelector(selectERC20TokensByChain);
44+
const variation = chainId;
45+
const { iconUrl: image } =
46+
erc20TokensByChain?.[variation]?.data?.[tokenAddress] ?? {};
47+
3948
if (tokenAddress !== NATIVE_TOKEN_ADDRESS) {
4049
return (
4150
<Box data-testid="token-icon">
42-
<Identicon
43-
address={tokenAddress}
44-
diameter={size === GasFeeTokenIconSize.Md ? 32 : 12}
45-
/>
51+
{image ? (
52+
<Identicon
53+
address={tokenAddress}
54+
diameter={size === GasFeeTokenIconSize.Md ? 32 : 12}
55+
image={image}
56+
/>
57+
) : (
58+
<PreferredAvatar
59+
address={tokenAddress}
60+
size={
61+
size === GasFeeTokenIconSize.Md
62+
? AvatarAccountSize.Md
63+
: AvatarAccountSize.Xs
64+
}
65+
/>
66+
)}
4667
</Box>
4768
);
4869
}

ui/pages/confirmations/hooks/useAutomaticGasFeeTokenSelect.test.ts

Lines changed: 110 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { NATIVE_TOKEN_ADDRESS } from '../../../../shared/constants/transaction';
66
import { genUnapprovedContractInteractionConfirmation } from '../../../../test/data/confirmations/contract-interaction';
77
import { getMockConfirmStateForTransaction } from '../../../../test/data/confirmations/helper';
88
import { renderHookWithConfirmContextProvider } from '../../../../test/lib/confirmations/render-helpers';
9+
import { flushPromises } from '../../../../test/lib/timer-helpers';
910
import { updateSelectedGasFeeToken } from '../../../store/controller-actions/transaction-controller';
1011
import { Alert } from '../../../ducks/confirm-alerts/confirm-alerts';
1112
import { forceUpdateMetamaskState } from '../../../store/actions';
@@ -46,6 +47,12 @@ function runHook({
4647
return { ...result, state };
4748
}
4849

50+
async function flushAsyncUpdates() {
51+
await act(async () => {
52+
await flushPromises();
53+
});
54+
}
55+
4956
describe('useAutomaticGasFeeTokenSelect', () => {
5057
const updateSelectedGasFeeTokenMock = jest.mocked(updateSelectedGasFeeToken);
5158
const forceUpdateMetamaskStateMock = jest.mocked(forceUpdateMetamaskState);
@@ -67,31 +74,48 @@ describe('useAutomaticGasFeeTokenSelect', () => {
6774
});
6875
});
6976

70-
it('selects first gas fee token', () => {
71-
runHook();
77+
it('selects first gas fee token', async () => {
78+
const { store } = runHook();
79+
80+
await flushAsyncUpdates();
7281

7382
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(1);
7483
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledWith(
7584
expect.any(String),
7685
GAS_FEE_TOKEN_MOCK.tokenAddress,
7786
);
87+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(1);
88+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledWith(store!.dispatch);
7889
});
7990

80-
it('does not select first gas fee token if gas fee token already selected', () => {
91+
it('does not select first gas fee token if gas fee token already selected', async () => {
8192
runHook({ selectedGasFeeToken: GAS_FEE_TOKEN_MOCK.tokenAddress });
93+
94+
await flushAsyncUpdates();
95+
8296
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
97+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0);
8398
});
8499

85-
it('does not select first gas fee token if no gas fee tokens', () => {
100+
it('does not select first gas fee token if no gas fee tokens', async () => {
86101
runHook({ gasFeeTokens: [] });
102+
103+
await flushAsyncUpdates();
104+
87105
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
106+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0);
88107
});
89108

90-
it('does not select first gas fee token if not first load', () => {
91-
const { rerender, state } = runHook({
109+
it('selects first gas fee token on rerender when selection becomes eligible', async () => {
110+
const { rerender, state, store } = runHook({
92111
selectedGasFeeToken: GAS_FEE_TOKEN_MOCK.tokenAddress,
93112
});
94113

114+
await flushAsyncUpdates();
115+
116+
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
117+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0);
118+
95119
const transactionMeta = state.metamask
96120
.transactions[0] as unknown as TransactionMeta;
97121

@@ -101,41 +125,92 @@ describe('useAutomaticGasFeeTokenSelect', () => {
101125

102126
rerender();
103127

104-
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
128+
await flushAsyncUpdates();
129+
130+
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(1);
131+
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledWith(
132+
expect.any(String),
133+
GAS_FEE_TOKEN_MOCK.tokenAddress,
134+
);
135+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(1);
136+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledWith(store!.dispatch);
105137
});
106138

107-
it('does not select first gas fee token if gasless not supported', () => {
139+
it('does not select first gas fee token if gasless not supported', async () => {
108140
useIsGaslessSupportedMock.mockReturnValue({
109141
isSupported: false,
110142
isSmartTransaction: false,
111143
});
112144

113145
runHook();
114146

147+
await flushAsyncUpdates();
148+
115149
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
150+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0);
116151
});
117152

118-
it('does not select first gas fee token if sufficient balance', () => {
153+
it('does not select first gas fee token if sufficient balance', async () => {
119154
useInsufficientBalanceAlertsMock.mockReturnValue([]);
120155

121156
runHook();
122157

158+
await flushAsyncUpdates();
159+
160+
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
161+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0);
162+
});
163+
164+
it('selects first gas fee token when insufficient balance appears after first render', async () => {
165+
let alerts: Alert[] = [];
166+
useInsufficientBalanceAlertsMock.mockImplementation(() => alerts);
167+
168+
const { rerender, store } = runHook({
169+
selectedGasFeeToken: undefined,
170+
});
171+
172+
await flushAsyncUpdates();
173+
123174
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
175+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0);
176+
177+
alerts = [{} as Alert];
178+
179+
rerender();
180+
181+
await flushAsyncUpdates();
182+
183+
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(1);
184+
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledWith(
185+
expect.any(String),
186+
GAS_FEE_TOKEN_MOCK.tokenAddress,
187+
);
188+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(1);
189+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledWith(store!.dispatch);
124190
});
125191

126-
it('does not select first gas fee token after firstCheck is set to false', () => {
127-
const { rerender, state } = runHook();
192+
it('does not select first gas fee token after firstCheck is set to false', async () => {
193+
const { rerender, state, store } = runHook();
194+
195+
await flushAsyncUpdates();
196+
128197
// Simulate a rerender with new state that would otherwise trigger selection
129198
act(() => {
130199
(
131200
state.metamask.transactions[0] as unknown as TransactionMeta
132201
).selectedGasFeeToken = undefined;
133202
});
203+
134204
rerender();
205+
206+
await flushAsyncUpdates();
207+
135208
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(1); // Only first run
209+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(1);
210+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledWith(store!.dispatch);
136211
});
137212

138-
it('does not select if transactionId is falsy', () => {
213+
it('does not select if transactionId is falsy', async () => {
139214
const state = getMockConfirmStateForTransaction(
140215
genUnapprovedContractInteractionConfirmation({
141216
gasFeeTokens: [GAS_FEE_TOKEN_MOCK],
@@ -145,15 +220,23 @@ describe('useAutomaticGasFeeTokenSelect', () => {
145220
// Remove transactionId
146221
state.metamask.transactions = [];
147222
renderHookWithConfirmContextProvider(useAutomaticGasFeeTokenSelect, state);
223+
224+
await flushAsyncUpdates();
225+
148226
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
227+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0);
149228
});
150229

151-
it('does not select if gasFeeTokens is falsy', () => {
230+
it('does not select if gasFeeTokens is falsy', async () => {
152231
runHook({ gasFeeTokens: [] });
232+
233+
await flushAsyncUpdates();
234+
153235
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
236+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0);
154237
});
155238

156-
it('does not select first gas fee token if 7702 and future native token', () => {
239+
it('does not select first gas fee token if 7702 and future native token', async () => {
157240
useIsGaslessSupportedMock.mockReturnValue({
158241
isSupported: true,
159242
isSmartTransaction: false,
@@ -168,16 +251,19 @@ describe('useAutomaticGasFeeTokenSelect', () => {
168251
],
169252
});
170253

254+
await flushAsyncUpdates();
255+
171256
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
257+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0);
172258
});
173259

174-
it('selects second gas fee token if 7702 and future native token', () => {
260+
it('selects second gas fee token if 7702 and future native token', async () => {
175261
useIsGaslessSupportedMock.mockReturnValue({
176262
isSupported: true,
177263
isSmartTransaction: false,
178264
});
179265

180-
runHook({
266+
const { store } = runHook({
181267
gasFeeTokens: [
182268
{
183269
...GAS_FEE_TOKEN_MOCK,
@@ -187,6 +273,14 @@ describe('useAutomaticGasFeeTokenSelect', () => {
187273
],
188274
});
189275

276+
await flushAsyncUpdates();
277+
190278
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(1);
279+
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledWith(
280+
expect.any(String),
281+
GAS_FEE_TOKEN_MOCK.tokenAddress,
282+
);
283+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(1);
284+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledWith(store!.dispatch);
191285
});
192286
});

ui/pages/confirmations/hooks/useAutomaticGasFeeTokenSelect.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,9 @@ export function useAutomaticGasFeeTokenSelect() {
5151
return;
5252
}
5353

54-
setFirstCheck(false);
55-
5654
if (shouldSelect) {
5755
await selectFirstToken();
56+
setFirstCheck(false);
5857
}
5958
}, [shouldSelect, selectFirstToken, firstCheck, gasFeeTokens, transactionId]);
6059
}

0 commit comments

Comments
 (0)