Skip to content

Commit 2f9e14a

Browse files
authored
Merge pull request #118 from hyperlane-xyz/main-to-nexus
Main to nexus
2 parents 0add7ae + cc06c51 commit 2f9e14a

22 files changed

+684
-76
lines changed

.eslintignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ coverage
55
postcss.config.js
66
next.config.js
77
tailwind.config.js
8-
jest.config.js
8+
jest.config.js
9+
sentry.*

.github/workflows/ci.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ name: ci
33
on:
44
# Triggers the workflow on push or pull request events but only for the main branch
55
push:
6-
branches: [main]
6+
branches: [main, nautilus, nexus]
77
pull_request:
8-
branches: [main]
8+
branches: [main, nautilus, nexus]
99

1010
# Allows you to run this workflow manually from the Actions tab
1111
workflow_dispatch:

next.config.js

+50-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,34 @@
11
/** @type {import('next').NextConfig} */
22

33
const { version } = require('./package.json')
4+
const { withSentryConfig } = require("@sentry/nextjs");
5+
const withBundleAnalyzer = require('@next/bundle-analyzer')({
6+
enabled: process.env.ANALYZE === 'true',
7+
})
48

59
const isDev = process.env.NODE_ENV !== 'production'
610

11+
// Sometimes useful to disable this during development
12+
const ENABLE_CSP_HEADER = true;
13+
const FRAME_SRC_HOSTS = ['https://*.walletconnect.com', 'https://*.walletconnect.org','https://*.solflare.com'];
14+
const STYLE_SRC_HOSTS = ['https://*.googleapis.com']
15+
const IMG_SRC_HOSTS = ['https://*.walletconnect.com'];
16+
const cspHeader = `
17+
default-src 'self';
18+
script-src 'self'${isDev ? " 'unsafe-eval'" : ''};
19+
style-src 'self' 'unsafe-inline' ${STYLE_SRC_HOSTS.join(' ')};
20+
connect-src *;
21+
img-src 'self' blob: data: ${IMG_SRC_HOSTS.join(' ')};
22+
font-src 'self' data:;
23+
object-src 'none';
24+
base-uri 'self';
25+
form-action 'self';
26+
frame-src 'self' ${FRAME_SRC_HOSTS.join(' ')};
27+
frame-ancestors 'none';
28+
${!isDev ? 'block-all-mixed-content;' : ''}
29+
${!isDev ? 'upgrade-insecure-requests;' : ''}
30+
`.replace(/\s{2,}/g, ' ').trim();
31+
732
const securityHeaders = [
833
{
934
key: 'X-XSS-Protection',
@@ -22,12 +47,14 @@ const securityHeaders = [
2247
value: 'strict-origin-when-cross-origin',
2348
},
2449
// Note, causes a problem for firefox: https://github.com/MetaMask/metamask-extension/issues/3133
25-
{
26-
key: 'Content-Security-Policy',
27-
value: `default-src 'self'; script-src 'self'${
28-
isDev ? " 'unsafe-eval'" : ''
29-
}; connect-src *; img-src 'self' data: https://*.walletconnect.com; style-src 'self' 'unsafe-inline' https://*.googleapis.com; font-src 'self' data:; base-uri 'self'; form-action 'self'; frame-src 'self' https://*.solflare.com https://*.walletconnect.com https://*.walletconnect.org;`,
30-
},
50+
...(ENABLE_CSP_HEADER
51+
? [
52+
{
53+
key: 'Content-Security-Policy',
54+
value: cspHeader,
55+
},
56+
]
57+
: [])
3158
]
3259

3360
const nextConfig = {
@@ -64,6 +91,22 @@ const nextConfig = {
6491

6592
reactStrictMode: true,
6693
swcMinify: true,
94+
95+
sentry: {
96+
hideSourceMaps: true,
97+
tunnelRoute: "/monitoring-tunnel",
98+
},
6799
}
68100

69-
module.exports = nextConfig
101+
const sentryWebpackPluginOptions = {
102+
org: "hyperlane",
103+
project: "warp-ui",
104+
authToken: process.env.SENTRY_AUTH_TOKEN,
105+
bundleSizeOptimizations: {
106+
excludeDebugStatements: true,
107+
excludeReplayIframe: true,
108+
excludeReplayShadowDom: true,
109+
},
110+
};
111+
112+
module.exports = withBundleAnalyzer(withSentryConfig(nextConfig, sentryWebpackPluginOptions));

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@
2121
"@hyperlane-xyz/widgets": "^3.1.4",
2222
"@metamask/jazzicon": "https://github.com/jmrossy/jazzicon#7a8df28974b4e81129bfbe3cab76308b889032a6",
2323
"@rainbow-me/rainbowkit": "1.3.0",
24+
"@sentry/nextjs": "^7.93.0",
2425
"@solana/spl-token": "^0.3.8",
2526
"@solana/wallet-adapter-base": "^0.9.22",
2627
"@solana/wallet-adapter-react": "^0.15.32",
2728
"@solana/wallet-adapter-react-ui": "^0.9.31",
2829
"@solana/wallet-adapter-wallets": "^0.19.16",
2930
"@solana/web3.js": "^1.77.0",
3031
"@tanstack/react-query": "^4.29.7",
32+
"@vercel/analytics": "^1.1.1",
3133
"bignumber.js": "^9.1.1",
3234
"buffer": "^6.0.3",
3335
"cosmjs-types": "^0.9.0",
@@ -43,6 +45,7 @@
4345
"zustand": "^4.3.9"
4446
},
4547
"devDependencies": {
48+
"@next/bundle-analyzer": "^14.0.4",
4649
"@trivago/prettier-plugin-sort-imports": "^4.1.1",
4750
"@types/jest": "^29.5.3",
4851
"@types/node": "^18.11.18",

sentry.client.config.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { sentryDefaultConfig } from './sentry.default.config';
2+
import * as Sentry from '@sentry/nextjs';
3+
4+
if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
5+
Sentry.init({
6+
...sentryDefaultConfig,
7+
integrations: [
8+
new Sentry.Integrations.Breadcrumbs({
9+
console: false,
10+
dom: false,
11+
fetch: false,
12+
history: false,
13+
sentry: false,
14+
xhr: false,
15+
}),
16+
new Sentry.Integrations.Dedupe(),
17+
new Sentry.Integrations.FunctionToString(),
18+
new Sentry.Integrations.GlobalHandlers(),
19+
new Sentry.Integrations.HttpContext(),
20+
],
21+
});
22+
}

sentry.default.config.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export const sentryDefaultConfig = {
2+
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
3+
tracesSampleRate: 1.0,
4+
maxBreadcrumbs: 1,
5+
sendClientReports: false,
6+
attachStacktrace: false,
7+
defaultIntegrations: false,
8+
integrations: [],
9+
beforeSend(event) {
10+
delete event.user;
11+
return event;
12+
},
13+
};

sentry.edge.config.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { sentryDefaultConfig } from './sentry.default.config';
2+
import * as Sentry from '@sentry/nextjs';
3+
4+
if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
5+
Sentry.init(sentryDefaultConfig)
6+
}

sentry.server.config.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { sentryDefaultConfig } from './sentry.default.config';
2+
import * as Sentry from '@sentry/nextjs';
3+
4+
if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
5+
Sentry.init(sentryDefaultConfig)
6+
}
+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/consts/config.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { ADDRESS_BLACKLIST } from './blacklist';
22

33
const isDevMode = process?.env?.NODE_ENV === 'development';
4-
const version = process?.env?.NEXT_PUBLIC_VERSION ?? null;
4+
const version = process?.env?.NEXT_PUBLIC_VERSION || '0.0.0';
55
const explorerApiKeys = JSON.parse(process?.env?.EXPLORER_API_KEYS || '{}');
66
const walletConnectProjectId = process?.env?.NEXT_PUBLIC_WALLET_CONNECT_ID || '';
77
const withdrawalWhitelist = process?.env?.NEXT_PUBLIC_BLOCK_WITHDRAWAL_WHITELIST || '';
88
const transferBlacklist = process?.env?.NEXT_PUBLIC_TRANSFER_BLACKLIST || '';
99

1010
interface Config {
1111
debug: boolean; // Enables some debug features in the app
12-
version: string | null; // Matches version number in package.json
12+
version: string; // Matches version number in package.json
1313
explorerApiKeys: Record<string, string>; // Optional map of API keys for block explorer
1414
showTipBox: boolean; // Show/Hide the blue tip box above the transfer form
1515
showDisabledTokens: boolean; // Show/Hide invalid token options in the selection modal

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

0 commit comments

Comments
 (0)