Skip to content

Commit ee9db42

Browse files
committed
Add error captures
1 parent cd09164 commit ee9db42

File tree

10 files changed

+76
-37
lines changed

10 files changed

+76
-37
lines changed
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useEffect } from 'react';
2+
import { toast } from 'react-toastify';
3+
4+
import { errorToString } from '@hyperlane-xyz/utils';
5+
6+
import { logger } from '../../utils/logger';
7+
8+
export function useToastError(error: any, errorMsg?: string) {
9+
useEffect(() => {
10+
if (!error) return;
11+
const message = errorMsg || errorToString(error, 500);
12+
logger.error(message, error);
13+
toast.error(errorMsg);
14+
}, [error, errorMsg]);
15+
}

src/features/caip/chains.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function tryParseCaip2Id(id?: ChainCaip2Id) {
3535
try {
3636
return parseCaip2Id(id);
3737
} catch (err) {
38-
logger.error('Error parsing caip2 id', err);
38+
logger.error(`Error parsing caip2 id ${id}`, err);
3939
return undefined;
4040
}
4141
}

src/features/caip/tokens.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export function tryParseCaip19Id(id?: TokenCaip19Id) {
6565
try {
6666
return parseCaip19Id(id);
6767
} catch (err) {
68-
logger.error('Error parsing caip2 id', err);
68+
logger.error(`Error parsing caip2 id ${id}`, err);
6969
return undefined;
7070
}
7171
}

src/features/tokens/approval.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useQuery } from '@tanstack/react-query';
22

33
import { ProtocolType, eqAddress } from '@hyperlane-xyz/utils';
44

5+
import { useToastError } from '../../components/toast/useToastError';
56
import { logger } from '../../utils/logger';
67
import { getProtocolType } from '../caip/chains';
78
import { getTokenAddress, isNativeToken, isNonFungibleToken } from '../caip/tokens';
@@ -20,11 +21,7 @@ export function useIsApproveRequired(
2021
) {
2122
const owner = useAccountAddressForChain(route?.originCaip2Id);
2223

23-
const {
24-
isLoading,
25-
isError: hasError,
26-
data,
27-
} = useQuery({
24+
const { isLoading, isError, error, data } = useQuery({
2825
queryKey: ['useIsApproveRequired', route, tokenCaip19Id, owner, amount],
2926
queryFn: async () => {
3027
if (!route || !tokenCaip19Id || !owner || !amount) return false;
@@ -33,7 +30,9 @@ export function useIsApproveRequired(
3330
enabled,
3431
});
3532

36-
return { isLoading, hasError, isApproveRequired: !!data };
33+
useToastError(error, 'Error fetching approval status');
34+
35+
return { isLoading, isError, isApproveRequired: !!data };
3736
}
3837

3938
export async function isApproveRequired(

src/features/tokens/balances.ts

+24-18
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useEffect } from 'react';
33

44
import { eqAddress, isValidAddress } from '@hyperlane-xyz/utils';
55

6+
import { useToastError } from '../../components/toast/useToastError';
67
import { logger } from '../../utils/logger';
78
import { getProtocolType } from '../caip/chains';
89
import { parseCaip19Id, tryGetChainIdFromToken } from '../caip/tokens';
@@ -23,11 +24,7 @@ export function useOriginBalance(
2324
const address = useAccountAddressForChain(originCaip2Id);
2425
const setSenderBalances = useStore((state) => state.setSenderBalances);
2526

26-
const {
27-
isLoading,
28-
isError: hasError,
29-
data,
30-
} = useQuery({
27+
const { isLoading, isError, error, data } = useQuery({
3128
queryKey: [
3229
'useOriginBalance',
3330
address,
@@ -58,13 +55,15 @@ export function useOriginBalance(
5855
refetchInterval: 5000,
5956
});
6057

58+
useToastError(error, 'Error fetching origin balance');
59+
6160
useEffect(() => {
6261
setSenderBalances(data?.tokenBalance || '0', data?.nativeBalance || '0');
6362
}, [data, setSenderBalances]);
6463

6564
return {
6665
isLoading,
67-
hasError,
66+
isError,
6867
tokenBalance: data?.tokenBalance,
6968
tokenDecimals: data?.tokenDecimals,
7069
nativeBalance: data?.nativeBalance,
@@ -75,11 +74,7 @@ export function useDestinationBalance(
7574
{ originCaip2Id, destinationCaip2Id, tokenCaip19Id, recipientAddress }: TransferFormValues,
7675
tokenRoutes: RoutesMap,
7776
) {
78-
const {
79-
isLoading,
80-
isError: hasError,
81-
data,
82-
} = useQuery({
77+
const { isLoading, isError, error, data } = useQuery({
8378
queryKey: [
8479
'useDestinationBalance',
8580
recipientAddress,
@@ -101,7 +96,9 @@ export function useDestinationBalance(
10196
refetchInterval: 5000,
10297
});
10398

104-
return { isLoading, hasError, balance: data?.balance, decimals: data?.decimals };
99+
useToastError(error, 'Error fetching destination balance');
100+
101+
return { isLoading, isError, balance: data?.balance, decimals: data?.decimals };
105102
}
106103

107104
// TODO solana support
@@ -112,7 +109,8 @@ export function useOriginTokenIdBalance(tokenCaip19Id: TokenCaip19Id) {
112109

113110
const {
114111
isLoading,
115-
isError: hasError,
112+
isError,
113+
error,
116114
data: tokenIds,
117115
} = useQuery({
118116
queryKey: ['useOriginTokenIdBalance', tokenCaip19Id, accountAddress],
@@ -123,11 +121,13 @@ export function useOriginTokenIdBalance(tokenCaip19Id: TokenCaip19Id) {
123121
refetchInterval: 5000,
124122
});
125123

124+
useToastError(error, 'Error fetching origin token IDs');
125+
126126
useEffect(() => {
127127
setSenderNftIds(tokenIds && Array.isArray(tokenIds) ? tokenIds : null);
128128
}, [tokenIds, setSenderNftIds]);
129129

130-
return { isLoading, hasError, tokenIds };
130+
return { isLoading, isError, tokenIds };
131131
}
132132

133133
// TODO solana support
@@ -159,7 +159,8 @@ export function useContractSupportsTokenByOwner(
159159
) {
160160
const {
161161
isLoading,
162-
isError: hasError,
162+
isError,
163+
error,
163164
data: isContractAllowToGetTokenIds,
164165
} = useQuery({
165166
queryKey: ['useContractSupportsTokenByOwner', tokenCaip19Id, accountAddress],
@@ -169,7 +170,9 @@ export function useContractSupportsTokenByOwner(
169170
},
170171
});
171172

172-
return { isLoading, hasError, isContractAllowToGetTokenIds };
173+
useToastError(error, 'Error ERC721 contract details');
174+
175+
return { isLoading, isError, isContractAllowToGetTokenIds };
173176
}
174177

175178
// TODO solana support
@@ -195,7 +198,8 @@ export function useIsSenderNftOwner(tokenCaip19Id: TokenCaip19Id, tokenId: strin
195198

196199
const {
197200
isLoading,
198-
isError: hasError,
201+
isError,
202+
error,
199203
data: owner,
200204
} = useQuery({
201205
queryKey: ['useOwnerOfErc721', tokenCaip19Id, tokenId],
@@ -205,12 +209,14 @@ export function useIsSenderNftOwner(tokenCaip19Id: TokenCaip19Id, tokenId: strin
205209
},
206210
});
207211

212+
useToastError(error, 'Error ERC721 owner');
213+
208214
useEffect(() => {
209215
if (!senderAddress || !owner) setIsSenderNftOwner(null);
210216
else setIsSenderNftOwner(eqAddress(senderAddress, owner));
211217
}, [owner, senderAddress, setIsSenderNftOwner]);
212218

213-
return { isLoading, hasError, owner };
219+
return { isLoading, isError, owner };
214220
}
215221

216222
// TODO solana support

src/features/tokens/routes/hooks.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useQuery } from '@tanstack/react-query';
22
import { useMemo } from 'react';
33

4+
import { useToastError } from '../../../components/toast/useToastError';
45
import { ibcRoutes } from '../../../consts/ibcRoutes';
56
import { logger } from '../../../utils/logger';
67
import { getChainIdFromToken } from '../../caip/tokens';
@@ -41,6 +42,8 @@ export function useTokenRoutes() {
4142
{ retry: false },
4243
);
4344

45+
useToastError(error, 'Error fetching token routes');
46+
4447
return { isLoading, error, tokenRoutes };
4548
}
4649

src/features/transfer/TransferTokenCard.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function TransferTokenCard() {
3333
<div className="my-32 flex flex-col items-center text-center">
3434
<h3 className="text-red-500">Error searching for token routes.</h3>
3535
<div className="mt-3 text-sm text-red-500">
36-
Please ensure chain and token configs are valid.
36+
Please ensure token configs are valid and RPCs are healthy.
3737
</div>
3838
<div className="mt-4 text-xs text-gray-500">{routesError.toString()}</div>
3939
</div>

src/features/transfer/TransfersDetailsModal.tsx

+5-3
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export function TransfersDetailsModal({
5454
const { address: tokenAddress, namespace: tokenNamespace } = parseCaip19Id(tokenCaip19Id);
5555
const isNative = tokenNamespace === AssetNamespace.native || isZeroishAddress(tokenAddress);
5656

57-
const getFormUrls = useCallback(async () => {
57+
const getMessageUrls = useCallback(async () => {
5858
try {
5959
if (originTxHash) {
6060
const originTxUrl = multiProvider.tryGetExplorerTxUrl(originChain, { hash: originTxHash });
@@ -73,8 +73,10 @@ export function TransfersDetailsModal({
7373

7474
useEffect(() => {
7575
if (!transfer) return;
76-
getFormUrls().catch((err) => logger.error(err));
77-
}, [transfer, getFormUrls]);
76+
getMessageUrls().catch((err) =>
77+
logger.error('Error getting message URLs for details modal', err),
78+
);
79+
}, [transfer, getMessageUrls]);
7880

7981
const isAccountReady = !!account?.isReady;
8082
const connectorName = account?.connectorName || 'wallet';

src/features/transfer/useIgpQuote.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useEffect } from 'react';
44
import { IHypTokenAdapter } from '@hyperlane-xyz/sdk';
55
import { ProtocolType, fromWei } from '@hyperlane-xyz/utils';
66

7+
import { useToastError } from '../../components/toast/useToastError';
78
import { COSM_IGP_QUOTE, SOL_IGP_QUOTE } from '../../consts/values';
89
import { getChainReference, getProtocolType } from '../caip/chains';
910
import { AssetNamespace, getCaip19Id, getNativeTokenAddress } from '../caip/tokens';
@@ -29,11 +30,7 @@ const DEFAULT_IGP_QUOTES = {
2930
export function useIgpQuote(route?: Route) {
3031
const setIgpQuote = useStore((state) => state.setIgpQuote);
3132

32-
const {
33-
isLoading,
34-
isError: hasError,
35-
data,
36-
} = useQuery({
33+
const { isLoading, isError, error, data } = useQuery({
3734
queryKey: ['useIgpQuote', route],
3835
queryFn: () => {
3936
if (!route || isIbcOnlyRoute(route)) return null;
@@ -45,7 +42,9 @@ export function useIgpQuote(route?: Route) {
4542
setIgpQuote(data || null);
4643
}, [data, setIgpQuote]);
4744

48-
return { isLoading, hasError, igpQuote: data };
45+
useToastError(error, 'Error fetching IGP quote');
46+
47+
return { isLoading, isError, igpQuote: data };
4948
}
5049

5150
export async function fetchIgpQuote(route: Route, adapter?: IHypTokenAdapter): Promise<IgpQuote> {

src/utils/logger.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
/* eslint-disable no-console */
2+
import { captureException } from '@sentry/nextjs';
3+
4+
import { getAddressProtocolType } from '@hyperlane-xyz/utils';
25

36
export const logger = {
47
debug: (...args: any[]) => console.debug(...args),
58
info: (...args: any[]) => console.info(...args),
69
warn: (...args: any[]) => console.warn(...args),
7-
error: (...args: any[]) => console.error(...args),
10+
error: (message: string, error: any, ...args: any[]) => {
11+
console.error(...args);
12+
const filteredArgs = args.filter(isSafeSentryArg);
13+
const extra = filteredArgs.reduce((acc, arg, i) => ({ ...acc, [`arg${i}`]: arg }), {});
14+
extra['message'] = message;
15+
captureException(error, { extra });
16+
},
817
};
18+
19+
function isSafeSentryArg(arg: any) {
20+
if (typeof arg == 'number') return true;
21+
if (typeof arg == 'string') return !getAddressProtocolType(arg) && arg.length < 1000;
22+
return false;
23+
}

0 commit comments

Comments
 (0)