Merge branch 'main' of github.com:blockscout/frontend into tom2drum/issue-1414
@@ -31,6 +31,7 @@ const moduleExports = {
|
||||
},
|
||||
);
|
||||
config.resolve.fallback = { fs: false, net: false, tls: false };
|
||||
config.externals.push('pino-pretty', 'lokijs', 'encoding');
|
||||
|
||||
return config;
|
||||
},
|
||||
|
||||
@@ -2,6 +2,8 @@ import type CspDev from 'csp-dev';
|
||||
|
||||
import config from 'configs/app';
|
||||
|
||||
import { KEY_WORDS } from '../utils';
|
||||
|
||||
export function walletConnect(): CspDev.DirectiveDescriptor {
|
||||
if (!config.features.blockchainInteraction.isEnabled) {
|
||||
return {};
|
||||
@@ -9,11 +11,13 @@ export function walletConnect(): CspDev.DirectiveDescriptor {
|
||||
|
||||
return {
|
||||
'connect-src': [
|
||||
'*.web3modal.com',
|
||||
'*.walletconnect.com',
|
||||
'wss://relay.walletconnect.com',
|
||||
'wss://www.walletlink.org',
|
||||
],
|
||||
'img-src': [
|
||||
KEY_WORDS.BLOB,
|
||||
'*.walletconnect.com',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -54,8 +54,7 @@
|
||||
"@tanstack/react-query-devtools": "^5.4.3",
|
||||
"@types/papaparse": "^5.3.5",
|
||||
"@types/react-scroll": "^1.8.4",
|
||||
"@web3modal/ethereum": "^2.6.2",
|
||||
"@web3modal/react": "^2.6.2",
|
||||
"@web3modal/wagmi": "3.5.0",
|
||||
"bignumber.js": "^9.1.0",
|
||||
"blo": "^1.1.1",
|
||||
"chakra-react-select": "^4.4.3",
|
||||
@@ -93,8 +92,8 @@
|
||||
"react-scroll": "^1.8.7",
|
||||
"swagger-ui-react": "^5.9.0",
|
||||
"use-font-face-observer": "^1.2.1",
|
||||
"viem": "^1.1.8",
|
||||
"wagmi": "^1.3.3",
|
||||
"viem": "1.20.1",
|
||||
"wagmi": "1.4.12",
|
||||
"xss": "^1.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ChakraProvider } from '@chakra-ui/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { w3mProvider } from '@web3modal/ethereum';
|
||||
import { createWeb3Modal, defaultWagmiConfig } from '@web3modal/wagmi/react';
|
||||
import React from 'react';
|
||||
import { configureChains, createConfig, WagmiConfig } from 'wagmi';
|
||||
import { WagmiConfig } from 'wagmi';
|
||||
import { mainnet } from 'wagmi/chains';
|
||||
|
||||
import type { Props as PageProps } from 'nextjs/getServerSideProps';
|
||||
@@ -33,17 +33,18 @@ const defaultAppContext = {
|
||||
};
|
||||
|
||||
// >>> Web3 stuff
|
||||
const { publicClient } = configureChains(
|
||||
[ mainnet ],
|
||||
[
|
||||
w3mProvider({ projectId: '' }),
|
||||
],
|
||||
);
|
||||
const chains = [ mainnet ];
|
||||
const WALLET_CONNECT_PROJECT_ID = 'PROJECT_ID';
|
||||
|
||||
const wagmiConfig = createConfig({
|
||||
autoConnect: false,
|
||||
connectors: [ ],
|
||||
publicClient,
|
||||
const wagmiConfig = defaultWagmiConfig({
|
||||
chains,
|
||||
projectId: WALLET_CONNECT_PROJECT_ID,
|
||||
});
|
||||
|
||||
createWeb3Modal({
|
||||
wagmiConfig,
|
||||
projectId: WALLET_CONNECT_PROJECT_ID,
|
||||
chains,
|
||||
});
|
||||
// <<<<
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Alert, Button, Flex } from '@chakra-ui/react';
|
||||
import { useWeb3Modal } from '@web3modal/react';
|
||||
import { useWeb3Modal, useWeb3ModalState } from '@web3modal/wagmi/react';
|
||||
import React from 'react';
|
||||
import { useAccount, useDisconnect } from 'wagmi';
|
||||
|
||||
@@ -8,7 +8,8 @@ import * as mixpanel from 'lib/mixpanel/index';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
|
||||
const ContractConnectWallet = () => {
|
||||
const { open, isOpen } = useWeb3Modal();
|
||||
const { open } = useWeb3Modal();
|
||||
const { open: isOpen } = useWeb3ModalState();
|
||||
const { disconnect } = useDisconnect();
|
||||
const isMobile = useIsMobile();
|
||||
const [ isModalOpening, setIsModalOpening ] = React.useState(false);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Alert, Box, Button, chakra, Flex, Link, Radio, RadioGroup } from '@chakra-ui/react';
|
||||
import { useWeb3Modal } from '@web3modal/react';
|
||||
import { useWeb3Modal } from '@web3modal/wagmi/react';
|
||||
import React from 'react';
|
||||
import type { SubmitHandler } from 'react-hook-form';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
@@ -8,8 +8,8 @@ import config from 'configs/app';
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import { WEI } from 'lib/consts';
|
||||
import { HOMEPAGE_STATS } from 'stubs/stats';
|
||||
import GasInfoTooltipContent from 'ui/shared/GasInfoTooltipContent/GasInfoTooltipContent';
|
||||
|
||||
import StatsGasPrices from './StatsGasPrices';
|
||||
import StatsItem from './StatsItem';
|
||||
|
||||
const hasGasTracker = config.UI.homepage.showGasTracker;
|
||||
@@ -45,7 +45,7 @@ const Stats = () => {
|
||||
!data.gas_prices && itemsCount--;
|
||||
data.rootstock_locked_btc && itemsCount++;
|
||||
const isOdd = Boolean(itemsCount % 2);
|
||||
const gasLabel = hasGasTracker && data.gas_prices ? <StatsGasPrices gasPrices={ data.gas_prices }/> : null;
|
||||
const gasLabel = hasGasTracker && data.gas_prices ? <GasInfoTooltipContent gasPrices={ data.gas_prices }/> : null;
|
||||
|
||||
content = (
|
||||
<>
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
|
||||
import type { GasPrices } from 'types/api/stats';
|
||||
|
||||
const StatsGasPrices = ({ gasPrices }: {gasPrices: GasPrices}) => {
|
||||
const GasInfoTooltipContent = ({ gasPrices }: {gasPrices: GasPrices}) => {
|
||||
const nameStyleProps = {
|
||||
color: useColorModeValue('blue.100', 'blue.600'),
|
||||
};
|
||||
@@ -20,4 +20,4 @@ const StatsGasPrices = ({ gasPrices }: {gasPrices: GasPrices}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default StatsGasPrices;
|
||||
export default React.memo(GasInfoTooltipContent);
|
||||
@@ -1,12 +1,14 @@
|
||||
import { useColorModeValue, useToken } from '@chakra-ui/react';
|
||||
import { useColorMode } from '@chakra-ui/react';
|
||||
import { jsonRpcProvider } from '@wagmi/core/providers/jsonRpc';
|
||||
import { EthereumClient, w3mConnectors } from '@web3modal/ethereum';
|
||||
import { Web3Modal } from '@web3modal/react';
|
||||
import { createWeb3Modal, useWeb3ModalTheme, defaultWagmiConfig } from '@web3modal/wagmi/react';
|
||||
import React from 'react';
|
||||
import type { Chain } from 'wagmi';
|
||||
import { configureChains, createConfig, WagmiConfig } from 'wagmi';
|
||||
import { configureChains, WagmiConfig } from 'wagmi';
|
||||
|
||||
import config from 'configs/app';
|
||||
import colors from 'theme/foundations/colors';
|
||||
import { BODY_TYPEFACE } from 'theme/foundations/typography';
|
||||
import zIndices from 'theme/foundations/zIndices';
|
||||
|
||||
const feature = config.features.blockchainInteraction;
|
||||
|
||||
@@ -41,58 +43,71 @@ const getConfig = () => {
|
||||
},
|
||||
};
|
||||
|
||||
const chains = [ currentChain ];
|
||||
|
||||
const { publicClient } = configureChains(chains, [
|
||||
jsonRpcProvider({
|
||||
rpc: () => ({
|
||||
http: config.chain.rpcUrl || '',
|
||||
const { chains } = configureChains(
|
||||
[ currentChain ],
|
||||
[
|
||||
jsonRpcProvider({
|
||||
rpc: () => ({
|
||||
http: config.chain.rpcUrl || '',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
const wagmiConfig = createConfig({
|
||||
autoConnect: true,
|
||||
connectors: w3mConnectors({ projectId: feature.walletConnect.projectId, chains }),
|
||||
publicClient,
|
||||
});
|
||||
const ethereumClient = new EthereumClient(wagmiConfig, chains);
|
||||
],
|
||||
);
|
||||
|
||||
return { wagmiConfig, ethereumClient };
|
||||
const wagmiConfig = defaultWagmiConfig({
|
||||
chains,
|
||||
projectId: feature.walletConnect.projectId,
|
||||
});
|
||||
|
||||
createWeb3Modal({
|
||||
wagmiConfig,
|
||||
projectId: feature.walletConnect.projectId,
|
||||
chains,
|
||||
themeVariables: {
|
||||
'--w3m-font-family': `${ BODY_TYPEFACE }, sans-serif`,
|
||||
'--w3m-accent': colors.blue[600],
|
||||
'--w3m-border-radius-master': '2px',
|
||||
'--w3m-z-index': zIndices.modal,
|
||||
},
|
||||
});
|
||||
|
||||
return { wagmiConfig };
|
||||
} catch (error) {
|
||||
return { wagmiConfig: undefined, ethereumClient: undefined };
|
||||
return { };
|
||||
}
|
||||
};
|
||||
|
||||
const { wagmiConfig, ethereumClient } = getConfig();
|
||||
const { wagmiConfig } = getConfig();
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
fallback?: JSX.Element | (() => JSX.Element);
|
||||
}
|
||||
|
||||
const Web3ModalProvider = ({ children, fallback }: Props) => {
|
||||
const modalZIndex = useToken<string>('zIndices', 'modal');
|
||||
const web3ModalTheme = useColorModeValue('light', 'dark');
|
||||
const Fallback = ({ children, fallback }: Props) => {
|
||||
return typeof fallback === 'function' ? fallback() : (fallback || <>{ children }</>); // eslint-disable-line react/jsx-no-useless-fragment
|
||||
};
|
||||
|
||||
if (!wagmiConfig || !ethereumClient || !feature.isEnabled) {
|
||||
return typeof fallback === 'function' ? fallback() : (fallback || <>{ children }</>); // eslint-disable-line react/jsx-no-useless-fragment
|
||||
const Provider = ({ children, fallback }: Props) => {
|
||||
const { colorMode } = useColorMode();
|
||||
const { setThemeMode } = useWeb3ModalTheme();
|
||||
|
||||
React.useEffect(() => {
|
||||
setThemeMode(colorMode);
|
||||
}, [ colorMode, setThemeMode ]);
|
||||
|
||||
// not really necessary, but we have to make typescript happy
|
||||
if (!wagmiConfig || !feature.isEnabled) {
|
||||
return <Fallback fallback={ fallback }>{ children }</Fallback>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<WagmiConfig config={ wagmiConfig }>
|
||||
{ children }
|
||||
</WagmiConfig>
|
||||
<Web3Modal
|
||||
projectId={ feature.walletConnect.projectId }
|
||||
ethereumClient={ ethereumClient }
|
||||
themeMode={ web3ModalTheme }
|
||||
themeVariables={{
|
||||
'--w3m-z-index': modalZIndex,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
<WagmiConfig config={ wagmiConfig }>
|
||||
{ children }
|
||||
</WagmiConfig>
|
||||
);
|
||||
};
|
||||
|
||||
const Web3ModalProvider = wagmiConfig && feature.isEnabled ? Provider : Fallback;
|
||||
|
||||
export default Web3ModalProvider;
|
||||
|
||||
@@ -19,7 +19,9 @@ test('default view +@dark-mode +@mobile', async({ mount, page }) => {
|
||||
</TestApp>,
|
||||
);
|
||||
|
||||
await component.getByLabel('color mode switch').click();
|
||||
await component.getByText(/gwei/i).hover();
|
||||
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1500, height: 220 } });
|
||||
|
||||
await component.getByLabel('color mode switch').click();
|
||||
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1500, height: 220 } });
|
||||
});
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import { Flex, Skeleton } from '@chakra-ui/react';
|
||||
import { Flex, LightMode, Link, Skeleton, Tooltip, chakra, useDisclosure } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import config from 'configs/app';
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import { HOMEPAGE_STATS } from 'stubs/stats';
|
||||
import GasInfoTooltipContent from 'ui/shared/GasInfoTooltipContent/GasInfoTooltipContent';
|
||||
import TextSeparator from 'ui/shared/TextSeparator';
|
||||
|
||||
const TopBarStats = () => {
|
||||
// have to implement controlled tooltip because of the issue - https://github.com/chakra-ui/chakra-ui/issues/7107
|
||||
const { isOpen, onOpen, onToggle, onClose } = useDisclosure();
|
||||
|
||||
const handleClick = React.useCallback((event: React.MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
onToggle();
|
||||
}, [ onToggle ]);
|
||||
|
||||
const { data, isPlaceholderData, isError } = useApiQuery('homepage_stats', {
|
||||
queryOptions: {
|
||||
placeholderData: HOMEPAGE_STATS,
|
||||
@@ -26,14 +35,34 @@ const TopBarStats = () => {
|
||||
>
|
||||
{ data?.coin_price && (
|
||||
<Skeleton isLoaded={ !isPlaceholderData }>
|
||||
<span>{ config.chain.governanceToken.symbol || config.chain.currency.symbol }: </span>
|
||||
<chakra.span color="text_secondary">{ config.chain.governanceToken.symbol || config.chain.currency.symbol } </chakra.span>
|
||||
<span>${ Number(data.coin_price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 }) }</span>
|
||||
</Skeleton>
|
||||
) }
|
||||
{ data?.coin_price && config.UI.homepage.showGasTracker && <TextSeparator color="divider"/> }
|
||||
{ data?.gas_prices && config.UI.homepage.showGasTracker && (
|
||||
<Skeleton isLoaded={ !isPlaceholderData }>
|
||||
<span>Gas: { data.gas_prices.average } Gwei</span>
|
||||
<chakra.span color="text_secondary">Gas </chakra.span>
|
||||
<LightMode>
|
||||
<Tooltip
|
||||
label={ <GasInfoTooltipContent gasPrices={ data.gas_prices }/> }
|
||||
hasArrow={ false }
|
||||
borderRadius="md"
|
||||
offset={ [ 0, 16 ] }
|
||||
bgColor="blackAlpha.900"
|
||||
p={ 0 }
|
||||
isOpen={ isOpen }
|
||||
>
|
||||
<Link
|
||||
_hover={{ textDecoration: 'none', color: 'link_hovered' }}
|
||||
onClick={ handleClick }
|
||||
onMouseEnter={ onOpen }
|
||||
onMouseLeave={ onClose }
|
||||
>
|
||||
{ data.gas_prices.average } Gwei
|
||||
</Link>
|
||||
</Tooltip>
|
||||
</LightMode>
|
||||
</Skeleton>
|
||||
) }
|
||||
</Flex>
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -1,11 +1,12 @@
|
||||
import { useWeb3Modal } from '@web3modal/react';
|
||||
import { useWeb3Modal, useWeb3ModalState } from '@web3modal/wagmi/react';
|
||||
import React from 'react';
|
||||
import { useAccount, useDisconnect } from 'wagmi';
|
||||
|
||||
import * as mixpanel from 'lib/mixpanel/index';
|
||||
|
||||
export default function useWallet() {
|
||||
const { open, isOpen } = useWeb3Modal();
|
||||
const { open } = useWeb3Modal();
|
||||
const { open: isOpen } = useWeb3ModalState();
|
||||
const { disconnect } = useDisconnect();
|
||||
const [ isModalOpening, setIsModalOpening ] = React.useState(false);
|
||||
const [ isClientLoaded, setIsClientLoaded ] = React.useState(false);
|
||||
|
||||