Skip to content

Commit a298d7b

Browse files
fix: Gas station improvements
1 parent 1ea605f commit a298d7b

File tree

3 files changed

+153
-29
lines changed

3 files changed

+153
-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: 120 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,50 @@ 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(
89+
store.dispatch,
90+
);
7891
});
7992

80-
it('does not select first gas fee token if gas fee token already selected', () => {
93+
it('does not select first gas fee token if gas fee token already selected', async () => {
8194
runHook({ selectedGasFeeToken: GAS_FEE_TOKEN_MOCK.tokenAddress });
95+
96+
await flushAsyncUpdates();
97+
8298
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
99+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0);
83100
});
84101

85-
it('does not select first gas fee token if no gas fee tokens', () => {
102+
it('does not select first gas fee token if no gas fee tokens', async () => {
86103
runHook({ gasFeeTokens: [] });
104+
105+
await flushAsyncUpdates();
106+
87107
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
108+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0);
88109
});
89110

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

116+
await flushAsyncUpdates();
117+
118+
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
119+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0);
120+
95121
const transactionMeta = state.metamask
96122
.transactions[0] as unknown as TransactionMeta;
97123

@@ -101,41 +127,98 @@ describe('useAutomaticGasFeeTokenSelect', () => {
101127

102128
rerender();
103129

104-
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
130+
await flushAsyncUpdates();
131+
132+
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(1);
133+
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledWith(
134+
expect.any(String),
135+
GAS_FEE_TOKEN_MOCK.tokenAddress,
136+
);
137+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(1);
138+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledWith(
139+
store.dispatch,
140+
);
105141
});
106142

107-
it('does not select first gas fee token if gasless not supported', () => {
143+
it('does not select first gas fee token if gasless not supported', async () => {
108144
useIsGaslessSupportedMock.mockReturnValue({
109145
isSupported: false,
110146
isSmartTransaction: false,
111147
});
112148

113149
runHook();
114150

151+
await flushAsyncUpdates();
152+
115153
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
154+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0);
116155
});
117156

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

121160
runHook();
122161

162+
await flushAsyncUpdates();
163+
123164
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
165+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0);
124166
});
125167

126-
it('does not select first gas fee token after firstCheck is set to false', () => {
127-
const { rerender, state } = runHook();
168+
it('selects first gas fee token when insufficient balance appears after first render', async () => {
169+
let alerts: Alert[] = [];
170+
useInsufficientBalanceAlertsMock.mockImplementation(() => alerts);
171+
172+
const { rerender, store } = runHook({
173+
selectedGasFeeToken: undefined,
174+
});
175+
176+
await flushAsyncUpdates();
177+
178+
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
179+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0);
180+
181+
alerts = [{} as Alert];
182+
183+
rerender();
184+
185+
await flushAsyncUpdates();
186+
187+
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(1);
188+
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledWith(
189+
expect.any(String),
190+
GAS_FEE_TOKEN_MOCK.tokenAddress,
191+
);
192+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(1);
193+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledWith(
194+
store.dispatch,
195+
);
196+
});
197+
198+
it('does not select first gas fee token after firstCheck is set to false', async () => {
199+
const { rerender, state, store } = runHook();
200+
201+
await flushAsyncUpdates();
202+
128203
// Simulate a rerender with new state that would otherwise trigger selection
129204
act(() => {
130205
(
131206
state.metamask.transactions[0] as unknown as TransactionMeta
132207
).selectedGasFeeToken = undefined;
133208
});
209+
134210
rerender();
211+
212+
await flushAsyncUpdates();
213+
135214
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(1); // Only first run
215+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(1);
216+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledWith(
217+
store.dispatch,
218+
);
136219
});
137220

138-
it('does not select if transactionId is falsy', () => {
221+
it('does not select if transactionId is falsy', async () => {
139222
const state = getMockConfirmStateForTransaction(
140223
genUnapprovedContractInteractionConfirmation({
141224
gasFeeTokens: [GAS_FEE_TOKEN_MOCK],
@@ -145,15 +228,23 @@ describe('useAutomaticGasFeeTokenSelect', () => {
145228
// Remove transactionId
146229
state.metamask.transactions = [];
147230
renderHookWithConfirmContextProvider(useAutomaticGasFeeTokenSelect, state);
231+
232+
await flushAsyncUpdates();
233+
148234
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
235+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0);
149236
});
150237

151-
it('does not select if gasFeeTokens is falsy', () => {
238+
it('does not select if gasFeeTokens is falsy', async () => {
152239
runHook({ gasFeeTokens: [] });
240+
241+
await flushAsyncUpdates();
242+
153243
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
244+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0);
154245
});
155246

156-
it('does not select first gas fee token if 7702 and future native token', () => {
247+
it('does not select first gas fee token if 7702 and future native token', async () => {
157248
useIsGaslessSupportedMock.mockReturnValue({
158249
isSupported: true,
159250
isSmartTransaction: false,
@@ -168,16 +259,19 @@ describe('useAutomaticGasFeeTokenSelect', () => {
168259
],
169260
});
170261

262+
await flushAsyncUpdates();
263+
171264
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(0);
265+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(0);
172266
});
173267

174-
it('selects second gas fee token if 7702 and future native token', () => {
268+
it('selects second gas fee token if 7702 and future native token', async () => {
175269
useIsGaslessSupportedMock.mockReturnValue({
176270
isSupported: true,
177271
isSmartTransaction: false,
178272
});
179273

180-
runHook({
274+
const { store } = runHook({
181275
gasFeeTokens: [
182276
{
183277
...GAS_FEE_TOKEN_MOCK,
@@ -187,6 +281,16 @@ describe('useAutomaticGasFeeTokenSelect', () => {
187281
],
188282
});
189283

284+
await flushAsyncUpdates();
285+
190286
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledTimes(1);
287+
expect(updateSelectedGasFeeTokenMock).toHaveBeenCalledWith(
288+
expect.any(String),
289+
GAS_FEE_TOKEN_MOCK.tokenAddress,
290+
);
291+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledTimes(1);
292+
expect(forceUpdateMetamaskStateMock).toHaveBeenCalledWith(
293+
store.dispatch,
294+
);
191295
});
192296
});

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)