Skip to content

Commit dbb0606

Browse files
authored
refactor: generalise useExecute hook (#2493)
1 parent 281e100 commit dbb0606

File tree

4 files changed

+135
-103
lines changed

4 files changed

+135
-103
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Web3Provider } from '@ethersproject/providers';
2+
3+
interface VerifyAndSwitchChainResponse {
4+
isChainCorrect: boolean;
5+
error?: string;
6+
}
7+
8+
/**
9+
* Ensure the provider is connected to the correct chain.
10+
* If not, attempts to switch to the desired chain.
11+
*/
12+
export const verifyAndSwitchChain = async (
13+
provider: Web3Provider,
14+
chainId: string,
15+
): Promise<VerifyAndSwitchChainResponse> => {
16+
if (!provider.provider.request) {
17+
return {
18+
isChainCorrect: false,
19+
error: 'Provider does not support the request method.',
20+
};
21+
}
22+
23+
try {
24+
const targetChainHex = `0x${parseInt(chainId, 10).toString(16)}`;
25+
const currentChainId = await provider.provider.request({
26+
method: 'eth_chainId',
27+
});
28+
29+
if (targetChainHex !== currentChainId) {
30+
await provider.provider.request({
31+
method: 'wallet_switchEthereumChain',
32+
params: [
33+
{
34+
chainId: targetChainHex,
35+
},
36+
],
37+
});
38+
}
39+
return { isChainCorrect: true };
40+
} catch (error) {
41+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
42+
return {
43+
isChainCorrect: false,
44+
error: errorMessage,
45+
};
46+
}
47+
};

packages/checkout/widgets-lib/src/lib/squid/hooks/useExecute.ts

Lines changed: 10 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,24 @@
11
import { Web3Provider } from '@ethersproject/providers';
2-
import { useContext } from 'react';
32
import { RouteResponse } from '@0xsquid/squid-types';
43
import { Squid } from '@0xsquid/sdk';
54
import { ethers } from 'ethers';
6-
import { Environment } from '@imtbl/config';
75

86
import { StatusResponse } from '@0xsquid/sdk/dist/types';
97
import { Flow } from '@imtbl/metrics';
108
import { EIP6963ProviderInfo } from '@imtbl/checkout-sdk';
119
import { isSquidNativeToken } from '../functions/isSquidNativeToken';
12-
import { useError } from './useError';
13-
import { AddTokensError, AddTokensErrorTypes } from '../../../widgets/add-tokens/types';
14-
import { EventTargetContext } from '../../../context/event-target-context/EventTargetContext';
15-
import { sendAddTokensFailedEvent } from '../../../widgets/add-tokens/AddTokensWidgetEvents';
1610
import { retry } from '../../retry';
1711
import { withMetricsAsync } from '../../metrics';
1812
import { useAnalytics, UserJourney } from '../../../context/analytics-provider/SegmentAnalyticsProvider';
1913
import { isRejectedError } from '../../../functions/errorType';
2014

2115
const TRANSACTION_NOT_COMPLETED = 'transaction not completed';
2216

23-
export const useExecute = (contextId: string, environment: Environment) => {
24-
const { showErrorHandover } = useError(environment);
17+
export const useExecute = (
18+
userJourney: UserJourney,
19+
onTransactionError?: (err: unknown) => void,
20+
) => {
2521
const { user } = useAnalytics();
26-
const {
27-
eventTargetState: { eventTarget },
28-
} = useContext(EventTargetContext);
2922

3023
const waitForReceipt = async (
3124
provider: Web3Provider,
@@ -59,83 +52,6 @@ export const useExecute = (contextId: string, environment: Environment) => {
5952
return result;
6053
};
6154

62-
const handleTransactionError = (err: unknown) => {
63-
const reason = `${
64-
(err as any)?.reason || (err as any)?.message || ''
65-
}`.toLowerCase();
66-
67-
let errorType = AddTokensErrorTypes.WALLET_FAILED;
68-
69-
if (reason.includes('failed') && reason.includes('open confirmation')) {
70-
errorType = AddTokensErrorTypes.WALLET_POPUP_BLOCKED;
71-
}
72-
73-
if (reason.includes('rejected') && reason.includes('user')) {
74-
errorType = AddTokensErrorTypes.WALLET_REJECTED;
75-
}
76-
77-
if (
78-
reason.includes('failed to submit')
79-
&& reason.includes('highest gas limit')
80-
) {
81-
errorType = AddTokensErrorTypes.WALLET_REJECTED_NO_FUNDS;
82-
}
83-
84-
if (
85-
reason.includes('status failed')
86-
|| reason.includes('transaction failed')
87-
) {
88-
errorType = AddTokensErrorTypes.TRANSACTION_FAILED;
89-
sendAddTokensFailedEvent(eventTarget, errorType);
90-
}
91-
92-
if (
93-
reason.includes('unrecognized chain')
94-
|| reason.includes('unrecognized chain')
95-
) {
96-
errorType = AddTokensErrorTypes.UNRECOGNISED_CHAIN;
97-
}
98-
99-
const error: AddTokensError = {
100-
type: errorType,
101-
data: { error: err },
102-
};
103-
104-
showErrorHandover(errorType, { contextId, error });
105-
};
106-
107-
// @TODO: Move to util function
108-
const checkProviderChain = async (
109-
provider: Web3Provider,
110-
chainId: string,
111-
): Promise<boolean> => {
112-
if (!provider.provider.request) {
113-
throw new Error('provider does not have request method');
114-
}
115-
try {
116-
const fromChainHex = `0x${parseInt(chainId, 10).toString(16)}`;
117-
const providerChainId = await provider.provider.request({
118-
method: 'eth_chainId',
119-
});
120-
121-
if (fromChainHex !== providerChainId) {
122-
await provider.provider.request({
123-
method: 'wallet_switchEthereumChain',
124-
params: [
125-
{
126-
chainId: fromChainHex,
127-
},
128-
],
129-
});
130-
return true;
131-
}
132-
return true;
133-
} catch (error) {
134-
handleTransactionError(error);
135-
return false;
136-
}
137-
};
138-
13955
const getAllowance = async (
14056
provider: Web3Provider,
14157
routeResponse: RouteResponse,
@@ -164,7 +80,7 @@ export const useExecute = (contextId: string, environment: Environment) => {
16480

16581
return ethers.constants.MaxUint256; // no approval is needed for native tokens
16682
} catch (error) {
167-
showErrorHandover(AddTokensErrorTypes.DEFAULT, { contextId, error });
83+
onTransactionError?.(error);
16884
return undefined;
16985
}
17086
};
@@ -220,14 +136,14 @@ export const useExecute = (contextId: string, environment: Environment) => {
220136
if (!isSquidNativeToken(routeResponse?.route?.params.fromToken)) {
221137
return await withMetricsAsync(
222138
(flow) => callApprove(flow, fromProviderInfo, provider, routeResponse),
223-
`${UserJourney.ADD_TOKENS}_Approve`,
139+
`${userJourney}_Approve`,
224140
await getAnonymousId(),
225141
(error) => (isRejectedError(error) ? 'rejected' : ''),
226142
);
227143
}
228144
return undefined;
229145
} catch (error) {
230-
handleTransactionError(error);
146+
onTransactionError?.(error);
231147
return undefined;
232148
}
233149
};
@@ -260,12 +176,12 @@ export const useExecute = (contextId: string, environment: Environment) => {
260176
try {
261177
return await withMetricsAsync(
262178
(flow) => callExecute(flow, squid, fromProviderInfo, provider, routeResponse),
263-
`${UserJourney.ADD_TOKENS}_Execute`,
179+
`${userJourney}_Execute`,
264180
await getAnonymousId(),
265181
(error) => (isRejectedError(error) ? 'rejected' : ''),
266182
);
267183
} catch (error) {
268-
handleTransactionError(error);
184+
onTransactionError?.(error);
269185
return undefined;
270186
}
271187
};
@@ -307,13 +223,12 @@ export const useExecute = (contextId: string, environment: Environment) => {
307223
},
308224
);
309225
} catch (error) {
310-
handleTransactionError(error);
226+
onTransactionError?.(error);
311227
return undefined;
312228
}
313229
};
314230

315231
return {
316-
checkProviderChain,
317232
getAllowance,
318233
approve,
319234
execute,
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { useContext } from 'react';
2+
import { AddTokensError, AddTokensErrorTypes } from '../types';
3+
import { EventTargetContext } from '../../../context/event-target-context/EventTargetContext';
4+
import { sendAddTokensFailedEvent } from '../AddTokensWidgetEvents';
5+
import { useError } from '../../../lib/squid/hooks/useError';
6+
import { AddTokensContext } from '../context/AddTokensContext';
7+
import { useProvidersContext } from '../../../context/providers-context/ProvidersContext';
8+
9+
export const useErrorHandler = () => {
10+
const {
11+
addTokensState: { id: contextId },
12+
} = useContext(AddTokensContext);
13+
14+
const {
15+
providersState: {
16+
checkout,
17+
},
18+
} = useProvidersContext();
19+
20+
const { showErrorHandover } = useError(checkout.config.environment);
21+
22+
const {
23+
eventTargetState: { eventTarget },
24+
} = useContext(EventTargetContext);
25+
26+
const onTransactionError = (err: unknown) => {
27+
const reason = `${(err as any)?.reason || (err as any)?.message || ''
28+
}`.toLowerCase();
29+
30+
let errorType = AddTokensErrorTypes.WALLET_FAILED;
31+
32+
if (reason.includes('failed') && reason.includes('open confirmation')) {
33+
errorType = AddTokensErrorTypes.WALLET_POPUP_BLOCKED;
34+
}
35+
36+
if (reason.includes('rejected') && reason.includes('user')) {
37+
errorType = AddTokensErrorTypes.WALLET_REJECTED;
38+
}
39+
40+
if (
41+
reason.includes('failed to submit')
42+
&& reason.includes('highest gas limit')
43+
) {
44+
errorType = AddTokensErrorTypes.WALLET_REJECTED_NO_FUNDS;
45+
}
46+
47+
if (
48+
reason.includes('status failed')
49+
|| reason.includes('transaction failed')
50+
) {
51+
errorType = AddTokensErrorTypes.TRANSACTION_FAILED;
52+
sendAddTokensFailedEvent(eventTarget, errorType);
53+
}
54+
55+
if (reason.includes('unrecognized chain')) {
56+
errorType = AddTokensErrorTypes.UNRECOGNISED_CHAIN;
57+
}
58+
59+
const error: AddTokensError = {
60+
type: errorType,
61+
data: { error: err },
62+
};
63+
64+
showErrorHandover(errorType, { contextId, error });
65+
};
66+
67+
return { onTransactionError };
68+
};

packages/checkout/widgets-lib/src/widgets/add-tokens/views/Review.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {
2525
import { RouteResponse } from '@0xsquid/squid-types';
2626
import { t } from 'i18next';
2727
import { Trans } from 'react-i18next';
28-
import { Environment } from '@imtbl/config';
2928
import { ChainId } from '@imtbl/checkout-sdk';
3029
import { trackFlow } from '@imtbl/metrics';
3130
import { SimpleLayout } from '../../../components/SimpleLayout/SimpleLayout';
@@ -57,7 +56,6 @@ import { getTotalRouteFees } from '../../../lib/squid/functions/getTotalRouteFee
5756
import { getRouteChains } from '../../../lib/squid/functions/getRouteChains';
5857

5958
import { SquidFooter } from '../../../lib/squid/components/SquidFooter';
60-
import { useError } from '../../../lib/squid/hooks/useError';
6159
import {
6260
sendAddTokensCloseEvent,
6361
sendAddTokensSuccessEvent,
@@ -73,6 +71,9 @@ import { RouteFees } from '../../../components/RouteFees/RouteFees';
7371
import { getDurationFormatted } from '../../../functions/getDurationFormatted';
7472
import { getFormattedNumber, getFormattedAmounts } from '../../../functions/getFormattedNumber';
7573
import { RiveStateMachineInput } from '../../../types/HandoverTypes';
74+
import { verifyAndSwitchChain } from '../../../lib/squid/functions/verifyAndSwitchChain';
75+
import { useErrorHandler } from '../hooks/useErrorHandler';
76+
import { useError } from '../../../lib/squid/hooks/useError';
7677

7778
interface ReviewProps {
7879
data: AddTokensReviewData;
@@ -111,6 +112,8 @@ export function Review({
111112
},
112113
} = useProvidersContext();
113114

115+
const { showErrorHandover } = useError(checkout.config.environment);
116+
114117
const {
115118
eventTargetState: { eventTarget },
116119
} = useContext(EventTargetContext);
@@ -126,11 +129,11 @@ export function Review({
126129
id: HandoverTarget.GLOBAL,
127130
});
128131

129-
const { showErrorHandover } = useError(checkout.config.environment);
132+
const { onTransactionError } = useErrorHandler();
130133

131134
const {
132-
checkProviderChain, getAllowance, approve, execute, getStatus,
133-
} = useExecute(id, checkout?.config.environment || Environment.SANDBOX);
135+
getAllowance, approve, execute, getStatus,
136+
} = useExecute(UserJourney.ADD_TOKENS, onTransactionError);
134137

135138
useEffect(() => {
136139
page({
@@ -359,12 +362,12 @@ export function Review({
359362
fromProvider,
360363
);
361364

362-
const isValidNetwork = await checkProviderChain(
365+
const verifyChainResult = await verifyAndSwitchChain(
363366
changeableProvider,
364367
route.route.params.fromChain,
365368
);
366369

367-
if (!isValidNetwork) {
370+
if (!verifyChainResult.isChainCorrect) {
368371
return;
369372
}
370373

@@ -654,7 +657,6 @@ export function Review({
654657
getRouteIntervalIdRef,
655658
approve,
656659
showHandover,
657-
checkProviderChain,
658660
getAllowance,
659661
execute,
660662
closeHandover,

0 commit comments

Comments
 (0)