Commit 76f2564e authored by tom goriunov's avatar tom goriunov Committed by GitHub

update `web3modal` to v4 (#1731)

* update hooks and methods

* fix typescript

* options fix

* fix pw test build

* try to fix tests

* more pw test fixes

* update screenshots and fix address verification test
parent 5a63649f
...@@ -51,3 +51,4 @@ NEXT_PUBLIC_STATS_API_HOST=https://localhost:3004 ...@@ -51,3 +51,4 @@ NEXT_PUBLIC_STATS_API_HOST=https://localhost:3004
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://localhost:3005 NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://localhost:3005
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://localhost:3006 NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://localhost:3006
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx
\ No newline at end of file
import type { WindowProvider } from 'wagmi'; import type { WalletProvider } from 'types/web3';
type CPreferences = { type CPreferences = {
zone: string; zone: string;
...@@ -8,7 +8,7 @@ type CPreferences = { ...@@ -8,7 +8,7 @@ type CPreferences = {
declare global { declare global {
export interface Window { export interface Window {
ethereum?: WindowProvider; ethereum?: WalletProvider | undefined;
coinzilla_display: Array<CPreferences>; coinzilla_display: Array<CPreferences>;
ga?: { ga?: {
getAll: () => Array<{ get: (prop: string) => string }>; getAll: () => Array<{ get: (prop: string) => string }>;
...@@ -27,3 +27,5 @@ declare global { ...@@ -27,3 +27,5 @@ declare global {
} }
} }
} }
export {};
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#a)" fill="currentColor"> <g clip-path="url(#a)" fill="currentColor">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.647 1h12.705A2.65 2.65 0 0 1 19 3.647v12.706A2.65 2.65 0 0 1 16.353 19H3.647A2.65 2.65 0 0 1 1 16.353V3.647A2.65 2.65 0 0 1 3.647 1Zm12.705 16.94a1.59 1.59 0 0 0 1.588-1.588l.001-1.87-2.647-2.206-3.192 2.66-5.77-5.246-4.273 4.747v1.915a1.59 1.59 0 0 0 1.588 1.588h12.705ZM15.293 10.9l2.647 2.205.001-9.457a1.59 1.59 0 0 0-1.588-1.588H3.647A1.59 1.59 0 0 0 2.06 3.647v9.208L6.257 8.19l5.874 5.342 3.162-2.634Zm1.06-4.605a2.65 2.65 0 0 1-2.647 2.647 2.65 2.65 0 0 1-2.647-2.648 2.65 2.65 0 0 1 2.647-2.647 2.65 2.65 0 0 1 2.647 2.647Zm-1.059 0a1.59 1.59 0 1 0-1.588 1.588 1.59 1.59 0 0 0 1.588-1.588Z"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M3.647 1h12.705A2.65 2.65 0 0 1 19 3.647v12.706A2.65 2.65 0 0 1 16.353 19H3.647A2.65 2.65 0 0 1 1 16.353V3.647A2.65 2.65 0 0 1 3.647 1Zm12.705 16.94a1.59 1.59 0 0 0 1.588-1.588l.001-1.87-2.647-2.206-3.192 2.66-5.77-5.246-4.273 4.747v1.915a1.59 1.59 0 0 0 1.588 1.588h12.705Zm-1.059-7.04 2.647 2.205.001-9.457a1.59 1.59 0 0 0-1.588-1.588H3.647A1.59 1.59 0 0 0 2.06 3.647v9.208L6.257 8.19l5.874 5.342 3.162-2.634Zm1.06-4.605a2.65 2.65 0 0 1-2.647 2.647 2.65 2.65 0 0 1-2.647-2.648 2.65 2.65 0 0 1 2.647-2.647 2.65 2.65 0 0 1 2.647 2.647Zm-1.059 0a1.59 1.59 0 1 0-1.588 1.588 1.59 1.59 0 0 0 1.588-1.588Z"/>
<path d="M19 3.647h.2-.2Zm-1.06 12.705h-.2.2Zm.001-1.87h.2v-.094l-.072-.06-.128.154Zm-2.647-2.206.128-.153-.128-.107-.128.107.128.153Zm-3.192 2.66-.134.148.129.117.133-.112-.128-.153ZM6.332 9.69l.134-.148-.149-.136-.134.15.148.134Zm-4.273 4.747-.149-.134-.051.057v.077h.2Zm15.881-1.333-.128.154.328.273v-.427h-.2ZM15.293 10.9l.128-.154-.128-.107-.128.107.128.154Zm2.648-7.252h.2-.2ZM2.06 12.855h-.2v.521l.349-.387-.15-.134ZM6.257 8.19l.134-.148-.149-.135-.134.15.149.133Zm5.874 5.342-.134.148.129.117.133-.112-.128-.153ZM16.351.8H3.648v.4h12.705V.8ZM19.2 3.647A2.85 2.85 0 0 0 16.352.8v.4A2.45 2.45 0 0 1 18.8 3.647h.4Zm0 12.706V3.647h-.4v12.706h.4ZM16.353 19.2a2.85 2.85 0 0 0 2.847-2.847h-.4a2.45 2.45 0 0 1-2.447 2.447v.4Zm-12.706 0h12.706v-.4H3.647v.4ZM.8 16.353A2.85 2.85 0 0 0 3.647 19.2v-.4A2.45 2.45 0 0 1 1.2 16.353H.8Zm0-12.706v12.706h.4V3.647H.8ZM3.647.8A2.85 2.85 0 0 0 .8 3.647h.4A2.45 2.45 0 0 1 3.647 1.2V.8ZM17.74 16.352a1.39 1.39 0 0 1-1.388 1.388v.4a1.79 1.79 0 0 0 1.788-1.788h-.4Zm.001-1.87v1.87h.4v-1.87h-.4Zm-2.575-2.052 2.647 2.205.256-.307-2.647-2.205-.256.307Zm-2.936 2.66 3.192-2.66-.256-.307-3.192 2.66.256.306ZM6.197 9.837l5.77 5.246.27-.296-5.771-5.246-.27.296Zm-3.99 4.733L6.48 9.823l-.297-.267-4.273 4.747.298.268Zm.052 1.78v-1.914h-.4v1.915h.4Zm1.388 1.39a1.39 1.39 0 0 1-1.388-1.39h-.4a1.79 1.79 0 0 0 1.788 1.79v-.4Zm12.705 0H3.647v.4h12.705v-.4Zm1.716-4.79-2.647-2.206-.256.307 2.647 2.206.256-.307Zm-.327-9.304v9.457h.4V3.647h-.4ZM16.353 2.26a1.39 1.39 0 0 1 1.388 1.388h.4a1.79 1.79 0 0 0-1.788-1.788v.4Zm-12.706 0h12.706v-.4H3.647v.4ZM2.26 3.647A1.39 1.39 0 0 1 3.647 2.26v-.4A1.79 1.79 0 0 0 1.86 3.647h.4Zm0 9.208V3.647h-.4v9.208h.4Zm3.849-4.798L1.91 12.721l.298.268 4.197-4.664-.297-.268Zm6.158 5.328L6.39 8.043l-.269.296 5.875 5.342.269-.296Zm2.899-2.64-3.162 2.634.256.307 3.162-2.634-.256-.307Zm-1.46-1.604a2.85 2.85 0 0 0 2.848-2.848h-.4a2.45 2.45 0 0 1-2.447 2.448v.4Zm-2.847-2.848a2.85 2.85 0 0 0 2.848 2.848v-.4a2.45 2.45 0 0 1-2.447-2.447h-.4Zm2.848-2.847a2.85 2.85 0 0 0-2.848 2.847h.4a2.45 2.45 0 0 1 2.448-2.447v-.4Zm2.847 2.847a2.85 2.85 0 0 0-2.847-2.847v.4a2.45 2.45 0 0 1 2.447 2.447h.4Zm-2.847-1.387a1.39 1.39 0 0 1 1.388 1.388h.4a1.79 1.79 0 0 0-1.788-1.788v.4Zm-1.389 1.388a1.39 1.39 0 0 1 1.389-1.388v-.4a1.79 1.79 0 0 0-1.789 1.788h.4Zm1.389 1.389a1.39 1.39 0 0 1-1.389-1.389h-.4a1.79 1.79 0 0 0 1.789 1.789v-.4Zm1.388-1.389a1.39 1.39 0 0 1-1.388 1.389v.4a1.79 1.79 0 0 0 1.788-1.789h-.4Z"/> <path d="M19 3.647h.2-.2Zm-1.06 12.705h-.2.2Zm.001-1.87h.2v-.094l-.072-.06-.128.154Zm-2.647-2.206.128-.153-.128-.107-.128.107.128.153Zm-3.192 2.66-.134.148.129.117.133-.112-.128-.153ZM6.332 9.69l.134-.148-.149-.136-.134.15.148.134Zm-4.273 4.747-.149-.134-.051.057v.077h.2Zm15.881-1.333-.128.154.328.273v-.427h-.2ZM15.293 10.9l.128-.154-.128-.107-.128.107.128.154Zm2.648-7.252h.2-.2ZM2.06 12.855h-.2v.521l.349-.387-.15-.134ZM6.257 8.19l.134-.148-.149-.135-.134.15.149.133Zm5.874 5.342-.134.148.129.117.133-.112-.128-.153ZM16.351.8H3.648v.4h12.705V.8ZM19.2 3.647A2.85 2.85 0 0 0 16.352.8v.4A2.45 2.45 0 0 1 18.8 3.647h.4Zm0 12.706V3.647h-.4v12.706h.4ZM16.353 19.2a2.85 2.85 0 0 0 2.847-2.847h-.4a2.45 2.45 0 0 1-2.447 2.447v.4Zm-12.706 0h12.706v-.4H3.647v.4ZM.8 16.353A2.85 2.85 0 0 0 3.647 19.2v-.4A2.45 2.45 0 0 1 1.2 16.353H.8Zm0-12.706v12.706h.4V3.647H.8ZM3.647.8A2.85 2.85 0 0 0 .8 3.647h.4A2.45 2.45 0 0 1 3.647 1.2V.8ZM17.74 16.352a1.39 1.39 0 0 1-1.388 1.388v.4a1.79 1.79 0 0 0 1.788-1.788h-.4Zm.001-1.87v1.87h.4v-1.87h-.4Zm-2.575-2.052 2.647 2.205.256-.307-2.647-2.205-.256.307Zm-2.936 2.66 3.192-2.66-.256-.307-3.192 2.66.256.306ZM6.197 9.837l5.77 5.246.27-.296-5.771-5.246-.27.296Zm-3.99 4.733L6.48 9.823l-.297-.267-4.273 4.747.298.268Zm.052 1.78v-1.914h-.4v1.915h.4Zm1.388 1.39a1.39 1.39 0 0 1-1.388-1.39h-.4a1.79 1.79 0 0 0 1.788 1.79v-.4Zm12.705 0H3.647v.4h12.705v-.4Zm1.716-4.79-2.647-2.206-.256.307 2.647 2.206.256-.307Zm-.327-9.304v9.457h.4V3.647h-.4ZM16.353 2.26a1.39 1.39 0 0 1 1.388 1.388h.4a1.79 1.79 0 0 0-1.788-1.788v.4Zm-12.706 0h12.706v-.4H3.647v.4ZM2.26 3.647A1.39 1.39 0 0 1 3.647 2.26v-.4A1.79 1.79 0 0 0 1.86 3.647h.4Zm0 9.208V3.647h-.4v9.208h.4Zm3.849-4.798L1.91 12.721l.298.268 4.197-4.664-.297-.268Zm6.158 5.328L6.39 8.043l-.269.296 5.875 5.342.269-.296Zm2.899-2.64-3.162 2.634.256.307 3.162-2.634-.256-.307Zm-1.46-1.604a2.85 2.85 0 0 0 2.848-2.848h-.4a2.45 2.45 0 0 1-2.447 2.448v.4Zm-2.847-2.848a2.85 2.85 0 0 0 2.848 2.848v-.4a2.45 2.45 0 0 1-2.447-2.447h-.4Zm2.848-2.847a2.85 2.85 0 0 0-2.848 2.847h.4a2.45 2.45 0 0 1 2.448-2.447v-.4Zm2.847 2.847a2.85 2.85 0 0 0-2.847-2.847v.4a2.45 2.45 0 0 1 2.447 2.447h.4Zm-2.847-1.387a1.39 1.39 0 0 1 1.388 1.388h.4a1.79 1.79 0 0 0-1.788-1.788v.4Zm-1.389 1.388a1.39 1.39 0 0 1 1.389-1.388v-.4a1.79 1.79 0 0 0-1.789 1.788h.4Zm1.389 1.389a1.39 1.39 0 0 1-1.389-1.389h-.4a1.79 1.79 0 0 0 1.789 1.789v-.4Zm1.388-1.389a1.39 1.39 0 0 1-1.388 1.389v.4a1.79 1.79 0 0 0 1.788-1.789h-.4Z"/>
</g> </g>
<defs> <defs>
......
...@@ -3,7 +3,7 @@ import { createPublicClient, http } from 'viem'; ...@@ -3,7 +3,7 @@ import { createPublicClient, http } from 'viem';
import currentChain from './currentChain'; import currentChain from './currentChain';
export const publicClient = (() => { export const publicClient = (() => {
if (currentChain.rpcUrls.public.http.filter(Boolean).length === 0) { if (currentChain.rpcUrls.default.http.filter(Boolean).length === 0) {
return; return;
} }
......
import type { Chain } from 'wagmi'; import { type Chain } from 'viem';
import config from 'configs/app'; import config from 'configs/app';
const currentChain: Chain = { const currentChain = {
id: Number(config.chain.id), id: Number(config.chain.id),
name: config.chain.name ?? '', name: config.chain.name ?? '',
network: config.chain.name ?? '',
nativeCurrency: { nativeCurrency: {
decimals: config.chain.currency.decimals, decimals: config.chain.currency.decimals,
name: config.chain.currency.name ?? '', name: config.chain.currency.name ?? '',
symbol: config.chain.currency.symbol ?? '', symbol: config.chain.currency.symbol ?? '',
}, },
rpcUrls: { rpcUrls: {
'public': {
http: [ config.chain.rpcUrl ?? '' ],
},
'default': { 'default': {
http: [ config.chain.rpcUrl ?? '' ], http: [ config.chain.rpcUrl ?? '' ],
}, },
...@@ -25,6 +21,7 @@ const currentChain: Chain = { ...@@ -25,6 +21,7 @@ const currentChain: Chain = {
url: config.app.baseUrl, url: config.app.baseUrl,
}, },
}, },
}; testnet: config.chain.isTestnet,
} as const satisfies Chain;
export default currentChain; export default currentChain;
import React from 'react'; import React from 'react';
import type { WindowProvider } from 'wagmi';
import 'wagmi/window';
import type { WalletType } from 'types/client/wallets'; import type { WalletType } from 'types/client/wallets';
import type { WalletProvider } from 'types/web3';
import config from 'configs/app'; import config from 'configs/app';
const feature = config.features.web3Wallet; const feature = config.features.web3Wallet;
export default function useProvider() { export default function useProvider() {
const [ provider, setProvider ] = React.useState<WindowProvider>(); const [ provider, setProvider ] = React.useState<WalletProvider>();
const [ wallet, setWallet ] = React.useState<WalletType>(); const [ wallet, setWallet ] = React.useState<WalletType>();
const initializeProvider = React.useMemo(() => async() => { const initializeProvider = React.useMemo(() => async() => {
......
import { defaultWagmiConfig } from '@web3modal/wagmi/react/config';
import { http } from 'viem';
import type { CreateConfigParameters } from 'wagmi';
import config from 'configs/app';
import currentChain from 'lib/web3/currentChain';
const feature = config.features.blockchainInteraction;
const wagmiConfig = (() => {
try {
if (!feature.isEnabled) {
throw new Error();
}
const chains: CreateConfigParameters['chains'] = [ currentChain ];
const wagmiConfig = defaultWagmiConfig({
chains,
multiInjectedProviderDiscovery: true,
transports: {
[currentChain.id]: http(),
},
projectId: feature.walletConnect.projectId,
metadata: {
name: `${ config.chain.name } explorer`,
description: `${ config.chain.name } explorer`,
url: config.app.baseUrl,
icons: [ config.UI.sidebar.icon.default ].filter(Boolean),
},
enableEmail: true,
});
return wagmiConfig;
} catch (error) {}
})();
export default wagmiConfig;
...@@ -60,17 +60,26 @@ const config: PlaywrightTestConfig = defineConfig({ ...@@ -60,17 +60,26 @@ const config: PlaywrightTestConfig = defineConfig({
minify: false, minify: false,
}, },
resolve: { resolve: {
alias: { alias: [
// There is an issue with building these package using vite that I cannot resolve // There is an issue with building these package using vite that I cannot resolve
// The solution described here - https://github.com/vitejs/vite/issues/9703#issuecomment-1216662109 // The solution described here - https://github.com/vitejs/vite/issues/9703#issuecomment-1216662109
// doesn't seam to work well with our setup // doesn't seam to work well with our setup
// so for now we just mock these modules in tests // so for now we just mock these modules in tests
'@metamask/post-message-stream': './playwright/mocks/modules/@metamask/post-message-stream.js', { find: '@metamask/post-message-stream', replacement: './playwright/mocks/modules/@metamask/post-message-stream.js' },
'@metamask/providers': './playwright/mocks/modules/@metamask/providers.js', { find: '@metamask/providers', replacement: './playwright/mocks/modules/@metamask/providers.js' },
// '@metamask/sdk imports the browser module as UMD, but @wagmi/connectors expects it to be ESM
// so we do a little remapping here
{ find: '@metamask/sdk', replacement: './node_modules/@metamask/sdk/dist/browser/es/metamask-sdk.js' },
// Mock for growthbook to test feature flags // Mock for growthbook to test feature flags
'lib/growthbook/useFeatureValue': './playwright/mocks/lib/growthbook/useFeatureValue.js', { find: 'lib/growthbook/useFeatureValue', replacement: './playwright/mocks/lib/growthbook/useFeatureValue.js' },
},
// The createWeb3Modal() function from web3modal/wagmi/react somehow pollutes the global styles which causes the tests to fail
// We don't call this function in TestApp and since we use useWeb3Modal() and useWeb3ModalState() hooks in the code, we have to mock the module
// Otherwise it will complain that createWeb3Modal() is no called before the hooks are used
{ find: /^@web3modal\/wagmi\/react$/, replacement: './playwright/mocks/modules/@web3modal/wagmi/react.js' },
],
}, },
define: { define: {
'process.env': '__envs', // Port process.env over window.__envs 'process.env': '__envs', // Port process.env over window.__envs
......
import { ChakraProvider } from '@chakra-ui/react'; import { ChakraProvider } from '@chakra-ui/react';
import { GrowthBookProvider } from '@growthbook/growthbook-react'; import { GrowthBookProvider } from '@growthbook/growthbook-react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createWeb3Modal, defaultWagmiConfig } from '@web3modal/wagmi/react';
import React from 'react'; import React from 'react';
import { WagmiConfig } from 'wagmi'; import { WagmiProvider } from 'wagmi';
import { mainnet } from 'wagmi/chains';
import type { Props as PageProps } from 'nextjs/getServerSideProps'; import type { Props as PageProps } from 'nextjs/getServerSideProps';
import { AppContextProvider } from 'lib/contexts/app'; import { AppContextProvider } from 'lib/contexts/app';
import { SocketProvider } from 'lib/socket/context'; import { SocketProvider } from 'lib/socket/context';
import wagmiConfig from 'lib/web3/wagmiConfig';
import * as app from 'playwright/utils/app'; import * as app from 'playwright/utils/app';
import theme from 'theme'; import theme from 'theme';
...@@ -34,23 +33,6 @@ const defaultAppContext = { ...@@ -34,23 +33,6 @@ const defaultAppContext = {
}, },
}; };
// >>> Web3 stuff
const chains = [ mainnet ];
const WALLET_CONNECT_PROJECT_ID = 'PROJECT_ID';
const wagmiConfig = defaultWagmiConfig({
chains,
projectId: WALLET_CONNECT_PROJECT_ID,
enableEmail: true,
});
createWeb3Modal({
wagmiConfig,
projectId: WALLET_CONNECT_PROJECT_ID,
chains,
});
// <<<<
const TestApp = ({ children, withSocket, appContext = defaultAppContext }: Props) => { const TestApp = ({ children, withSocket, appContext = defaultAppContext }: Props) => {
const [ queryClient ] = React.useState(() => new QueryClient({ const [ queryClient ] = React.useState(() => new QueryClient({
defaultOptions: { defaultOptions: {
...@@ -67,9 +49,9 @@ const TestApp = ({ children, withSocket, appContext = defaultAppContext }: Props ...@@ -67,9 +49,9 @@ const TestApp = ({ children, withSocket, appContext = defaultAppContext }: Props
<SocketProvider url={ withSocket ? `ws://${ app.domain }:${ app.socketPort }` : undefined }> <SocketProvider url={ withSocket ? `ws://${ app.domain }:${ app.socketPort }` : undefined }>
<AppContextProvider { ...appContext }> <AppContextProvider { ...appContext }>
<GrowthBookProvider> <GrowthBookProvider>
<WagmiConfig config={ wagmiConfig }> <WagmiProvider config={ wagmiConfig! }>
{ children } { children }
</WagmiConfig> </WagmiProvider>
</GrowthBookProvider> </GrowthBookProvider>
</AppContextProvider> </AppContextProvider>
</SocketProvider> </SocketProvider>
......
function useWeb3Modal() {
return {
open: () => {},
};
}
function useWeb3ModalState() {
return {
isOpen: false,
};
}
function useWeb3ModalTheme() {
return {
setThemeMode: () => {},
};
}
function createWeb3Modal() {}
export {
createWeb3Modal,
useWeb3Modal,
useWeb3ModalState,
useWeb3ModalTheme,
};
...@@ -84,6 +84,8 @@ export const GET_BLOCK: GetBlockReturnType<Chain, false, 'latest'> = { ...@@ -84,6 +84,8 @@ export const GET_BLOCK: GetBlockReturnType<Chain, false, 'latest'> = {
withdrawals: Array(10).fill(WITHDRAWAL), withdrawals: Array(10).fill(WITHDRAWAL),
withdrawalsRoot: TX_HASH, withdrawalsRoot: TX_HASH,
sealFields: [ '0x00' ], sealFields: [ '0x00' ],
blobGasUsed: BigInt(0),
excessBlobGas: BigInt(0),
}; };
export const GET_BLOCK_WITH_TRANSACTIONS: GetBlockReturnType<Chain, true, 'latest'> = { export const GET_BLOCK_WITH_TRANSACTIONS: GetBlockReturnType<Chain, true, 'latest'> = {
......
...@@ -7,3 +7,7 @@ export type ExcludeNull<T> = T extends null ? never : T; ...@@ -7,3 +7,7 @@ export type ExcludeNull<T> = T extends null ? never : T;
export type ExcludeUndefined<T> = T extends undefined ? never : T; export type ExcludeUndefined<T> = T extends undefined ? never : T;
export type KeysOfObjectOrNull<T> = keyof ExcludeNull<T>; export type KeysOfObjectOrNull<T> = keyof ExcludeNull<T>;
/** Combines members of an intersection into a readable type. */
// https://twitter.com/mattpocockuk/status/1622730173446557697?s=20&t=NdpAcmEFXY01xkqU3KO0Mg
export type Evaluate<Type> = { [key in keyof Type]: Type[key] } & unknown
// copied from node_modules/@wagmi/core/src/connectors/injected.ts
import type { EIP1193Provider } from 'viem';
import type { Evaluate } from './utils';
type WalletProviderFlags =
| 'isApexWallet'
| 'isAvalanche'
| 'isBackpack'
| 'isBifrost'
| 'isBitKeep'
| 'isBitski'
| 'isBlockWallet'
| 'isBraveWallet'
| 'isCoinbaseWallet'
| 'isDawn'
| 'isEnkrypt'
| 'isExodus'
| 'isFrame'
| 'isFrontier'
| 'isGamestop'
| 'isHyperPay'
| 'isImToken'
| 'isKuCoinWallet'
| 'isMathWallet'
| 'isMetaMask'
| 'isOkxWallet'
| 'isOKExWallet'
| 'isOneInchAndroidWallet'
| 'isOneInchIOSWallet'
| 'isOpera'
| 'isPhantom'
| 'isPortal'
| 'isRabby'
| 'isRainbow'
| 'isStatus'
| 'isTally'
| 'isTokenPocket'
| 'isTokenary'
| 'isTrust'
| 'isTrustWallet'
| 'isXDEFI'
| 'isZerion'
export type WalletProvider = Evaluate<
EIP1193Provider & {
[key in WalletProviderFlags]?: true | undefined
} & {
providers?: Array<WalletProvider> | undefined;
/** Only exists in MetaMask as of 2022/04/03 */
_events?: { connect?: (() => void) | undefined } | undefined;
/** Only exists in MetaMask as of 2022/04/03 */
_state?:
| {
accounts?: Array<string>;
initialized?: boolean;
isConnected?: boolean;
isPermanentlyDisconnected?: boolean;
isUnlocked?: boolean;
}
| undefined;
}
>
import { test, expect } from '@playwright/experimental-ct-react'; import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react'; import React from 'react';
import type { WindowProvider } from 'wagmi';
import type { WalletProvider } from 'types/web3';
import * as addressMock from 'mocks/address/address'; import * as addressMock from 'mocks/address/address';
import * as countersMock from 'mocks/address/counters'; import * as countersMock from 'mocks/address/counters';
...@@ -73,7 +74,7 @@ test('token', async({ mount, page }) => { ...@@ -73,7 +74,7 @@ test('token', async({ mount, page }) => {
await page.evaluate(() => { await page.evaluate(() => {
window.ethereum = { window.ethereum = {
providers: [ { isMetaMask: true, _events: {} } ], providers: [ { isMetaMask: true, _events: {} } ],
}as WindowProvider; } as WalletProvider;
}); });
const component = await mount( const component = await mount(
......
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import { useAccount, useWalletClient, useNetwork, useSwitchNetwork } from 'wagmi'; import { useAccount, useWalletClient, useSwitchChain } from 'wagmi';
import type { SmartContractWriteMethod } from 'types/api/contract'; import type { SmartContractWriteMethod } from 'types/api/contract';
...@@ -21,9 +21,8 @@ import { getNativeCoinValue, prepareAbi } from './utils'; ...@@ -21,9 +21,8 @@ import { getNativeCoinValue, prepareAbi } from './utils';
const ContractWrite = () => { const ContractWrite = () => {
const { data: walletClient } = useWalletClient(); const { data: walletClient } = useWalletClient();
const { isConnected } = useAccount(); const { isConnected, chainId } = useAccount();
const { chain } = useNetwork(); const { switchChainAsync } = useSwitchChain();
const { switchNetworkAsync } = useSwitchNetwork();
const router = useRouter(); const router = useRouter();
...@@ -45,14 +44,13 @@ const ContractWrite = () => { ...@@ -45,14 +44,13 @@ const ContractWrite = () => {
const contractAbi = useContractAbi({ addressHash, isProxy, isCustomAbi }); const contractAbi = useContractAbi({ addressHash, isProxy, isCustomAbi });
// TODO @tom2drum maybe move this inside the form
const handleMethodFormSubmit = React.useCallback(async(item: SmartContractWriteMethod, args: Array<unknown>) => { const handleMethodFormSubmit = React.useCallback(async(item: SmartContractWriteMethod, args: Array<unknown>) => {
if (!isConnected) { if (!isConnected) {
throw new Error('Wallet is not connected'); throw new Error('Wallet is not connected');
} }
if (chain?.id && String(chain.id) !== config.chain.id) { if (chainId && String(chainId) !== config.chain.id) {
await switchNetworkAsync?.(Number(config.chain.id)); await switchChainAsync?.({ chainId: Number(config.chain.id) });
} }
if (!contractAbi) { if (!contractAbi) {
...@@ -87,7 +85,7 @@ const ContractWrite = () => { ...@@ -87,7 +85,7 @@ const ContractWrite = () => {
}); });
return { hash }; return { hash };
}, [ isConnected, chain, contractAbi, walletClient, addressHash, switchNetworkAsync ]); }, [ isConnected, chainId, contractAbi, walletClient, addressHash, switchChainAsync ]);
const renderItemContent = React.useCallback((item: SmartContractWriteMethod, index: number, id: number) => { const renderItemContent = React.useCallback((item: SmartContractWriteMethod, index: number, id: number) => {
return ( return (
......
import React from 'react'; import React from 'react';
import { useWaitForTransaction } from 'wagmi'; import { useWaitForTransactionReceipt } from 'wagmi';
import type { ResultComponentProps } from './methodForm/types'; import type { ResultComponentProps } from './methodForm/types';
import type { ContractMethodWriteResult } from './types'; import type { ContractMethodWriteResult } from './types';
...@@ -9,7 +9,7 @@ import ContractWriteResultDumb from './ContractWriteResultDumb'; ...@@ -9,7 +9,7 @@ import ContractWriteResultDumb from './ContractWriteResultDumb';
const ContractWriteResult = ({ result, onSettle }: ResultComponentProps<SmartContractWriteMethod>) => { const ContractWriteResult = ({ result, onSettle }: ResultComponentProps<SmartContractWriteMethod>) => {
const txHash = result && 'hash' in result ? result.hash as `0x${ string }` : undefined; const txHash = result && 'hash' in result ? result.hash as `0x${ string }` : undefined;
const txInfo = useWaitForTransaction({ const txInfo = useWaitForTransactionReceipt({
hash: txHash, hash: txHash,
}); });
......
...@@ -8,7 +8,7 @@ import ContractWriteResultDumb from './ContractWriteResultDumb'; ...@@ -8,7 +8,7 @@ import ContractWriteResultDumb from './ContractWriteResultDumb';
test('loading', async({ mount }) => { test('loading', async({ mount }) => {
const props = { const props = {
txInfo: { txInfo: {
status: 'loading' as const, status: 'pending' as const,
error: null, error: null,
}, },
result: { result: {
......
...@@ -11,7 +11,7 @@ interface Props { ...@@ -11,7 +11,7 @@ interface Props {
result: ContractMethodWriteResult; result: ContractMethodWriteResult;
onSettle: () => void; onSettle: () => void;
txInfo: { txInfo: {
status: 'loading' | 'success' | 'error' | 'idle'; status: 'loading' | 'success' | 'error' | 'idle' | 'pending';
error: Error | null; error: Error | null;
}; };
} }
...@@ -20,7 +20,7 @@ const ContractWriteResultDumb = ({ result, onSettle, txInfo }: Props) => { ...@@ -20,7 +20,7 @@ const ContractWriteResultDumb = ({ result, onSettle, txInfo }: Props) => {
const txHash = result && 'hash' in result ? result.hash : undefined; const txHash = result && 'hash' in result ? result.hash : undefined;
React.useEffect(() => { React.useEffect(() => {
if (txInfo.status !== 'loading') { if (txInfo.status !== 'pending') {
onSettle(); onSettle();
} }
}, [ onSettle, txInfo.status ]); }, [ onSettle, txInfo.status ]);
...@@ -55,7 +55,7 @@ const ContractWriteResultDumb = ({ result, onSettle, txInfo }: Props) => { ...@@ -55,7 +55,7 @@ const ContractWriteResultDumb = ({ result, onSettle, txInfo }: Props) => {
); );
} }
case 'loading': { case 'pending': {
return ( return (
<> <>
<Spinner size="sm" mr={ 3 }/> <Spinner size="sm" mr={ 3 }/>
......
import { watchAccount, getAccount } from '@wagmi/core'; import { watchAccount, getAccount } from '@wagmi/core';
import React from 'react'; import React from 'react';
import type { Config } from 'wagmi';
import { useConfig } from 'wagmi';
export function getWalletAccount() { export function getWalletAccount(config: Config) {
try { try {
return getAccount(); return getAccount(config);
} catch (error) { } catch (error) {
return null; return null;
} }
} }
export default function useWatchAccount() { export default function useWatchAccount() {
const [ account, setAccount ] = React.useState(getWalletAccount()); const config = useConfig();
const [ account, setAccount ] = React.useState(getWalletAccount(config));
React.useEffect(() => { React.useEffect(() => {
if (!account) { if (!account) {
return; return;
} }
return watchAccount(setAccount); return watchAccount(config, {
}, [ account ]); onChange(account) {
setAccount(account);
},
});
}, [ account, config ]);
return account; return account;
} }
...@@ -80,15 +80,7 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre ...@@ -80,15 +80,7 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre
const onSubmit = handleSubmit(onFormSubmit); const onSubmit = handleSubmit(onFormSubmit);
const { signMessage, isLoading: isSigning } = useSignMessage({ const { signMessage, isPending: isSigning } = useSignMessage();
onSuccess: (data) => {
setValue('signature', data);
onSubmit();
},
onError: (error) => {
return setError('root', { type: 'SIGNING_FAIL', message: (error as Error)?.message || 'Oops! Something went wrong' });
},
});
const handleSignMethodChange = React.useCallback((value: typeof signMethod) => { const handleSignMethodChange = React.useCallback((value: typeof signMethod) => {
setSignMethod(value); setSignMethod(value);
...@@ -108,8 +100,16 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre ...@@ -108,8 +100,16 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre
} }
const message = getValues('message'); const message = getValues('message');
signMessage({ message }); signMessage({ message }, {
}, [ clearErrors, isConnected, getValues, signMessage, setError ]); onSuccess: (data) => {
setValue('signature', data);
onSubmit();
},
onError: (error) => {
return setError('root', { type: 'SIGNING_FAIL', message: (error as Error)?.message || 'Oops! Something went wrong' });
},
});
}, [ clearErrors, isConnected, getValues, signMessage, setError, setValue, onSubmit ]);
const handleManualSignClick = React.useCallback(() => { const handleManualSignClick = React.useCallback(() => {
clearErrors('root'); clearErrors('root');
...@@ -219,7 +219,7 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre ...@@ -219,7 +219,7 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre
{ !noWeb3Provider && ( { !noWeb3Provider && (
<RadioGroup onChange={ handleSignMethodChange } value={ signMethod } display="flex" flexDir="column" rowGap={ 4 }> <RadioGroup onChange={ handleSignMethodChange } value={ signMethod } display="flex" flexDir="column" rowGap={ 4 }>
<Radio value="wallet">Sign via Web3 wallet</Radio> <Radio value="wallet">Sign via Web3 wallet</Radio>
<Radio value="manually">Sign manually</Radio> <Radio value="manual">Sign manually</Radio>
</RadioGroup> </RadioGroup>
) } ) }
{ signMethod === 'manual' && <AddressVerificationFieldSignature formState={ formState } control={ control }/> } { signMethod === 'manual' && <AddressVerificationFieldSignature formState={ formState } control={ control }/> }
......
import type { TypedData } from 'abitype'; import type { TypedData } from 'abitype';
import { useCallback } from 'react'; import { useCallback } from 'react';
import type { Account, SignTypedDataParameters } from 'viem'; import type { Account, SignTypedDataParameters } from 'viem';
import { useAccount, useSendTransaction, useSwitchNetwork, useNetwork, useSignMessage, useSignTypedData } from 'wagmi'; import { useAccount, useSendTransaction, useSwitchChain, useSignMessage, useSignTypedData } from 'wagmi';
import config from 'configs/app'; import config from 'configs/app';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
...@@ -9,7 +9,7 @@ import * as mixpanel from 'lib/mixpanel/index'; ...@@ -9,7 +9,7 @@ import * as mixpanel from 'lib/mixpanel/index';
type SendTransactionArgs = { type SendTransactionArgs = {
chainId?: number; chainId?: number;
mode?: 'prepared'; mode?: 'prepared';
to: string; to: `0x${ string }`;
}; };
export type SignTypedDataArgs< export type SignTypedDataArgs<
...@@ -22,12 +22,11 @@ export type SignTypedDataArgs< ...@@ -22,12 +22,11 @@ export type SignTypedDataArgs<
> = SignTypedDataParameters<TTypedData, TPrimaryType, Account>; > = SignTypedDataParameters<TTypedData, TPrimaryType, Account>;
export default function useMarketplaceWallet(appId: string) { export default function useMarketplaceWallet(appId: string) {
const { address } = useAccount(); const { address, chainId } = useAccount();
const { chain } = useNetwork();
const { sendTransactionAsync } = useSendTransaction(); const { sendTransactionAsync } = useSendTransaction();
const { signMessageAsync } = useSignMessage(); const { signMessageAsync } = useSignMessage();
const { signTypedDataAsync } = useSignTypedData(); const { signTypedDataAsync } = useSignTypedData();
const { switchNetworkAsync } = useSwitchNetwork({ chainId: Number(config.chain.id) }); const { switchChainAsync } = useSwitchChain();
const logEvent = useCallback((event: mixpanel.EventPayload<mixpanel.EventTypes.WALLET_ACTION>['Action']) => { const logEvent = useCallback((event: mixpanel.EventPayload<mixpanel.EventTypes.WALLET_ACTION>['Action']) => {
mixpanel.logEvent( mixpanel.logEvent(
...@@ -37,16 +36,16 @@ export default function useMarketplaceWallet(appId: string) { ...@@ -37,16 +36,16 @@ export default function useMarketplaceWallet(appId: string) {
}, [ address, appId ]); }, [ address, appId ]);
const switchNetwork = useCallback(async() => { const switchNetwork = useCallback(async() => {
if (Number(config.chain.id) !== chain?.id) { if (Number(config.chain.id) !== chainId) {
await switchNetworkAsync?.(); await switchChainAsync?.({ chainId: Number(config.chain.id) });
} }
}, [ chain, switchNetworkAsync ]); }, [ chainId, switchChainAsync ]);
const sendTransaction = useCallback(async(transaction: SendTransactionArgs) => { const sendTransaction = useCallback(async(transaction: SendTransactionArgs) => {
await switchNetwork(); await switchNetwork();
const tx = await sendTransactionAsync(transaction); const tx = await sendTransactionAsync(transaction);
logEvent('Send Transaction'); logEvent('Send Transaction');
return tx.hash; return tx;
}, [ sendTransactionAsync, switchNetwork, logEvent ]); }, [ sendTransactionAsync, switchNetwork, logEvent ]);
const signMessage = useCallback(async(message: string) => { const signMessage = useCallback(async(message: string) => {
......
...@@ -114,6 +114,7 @@ test('address verification flow', async({ mount, page }) => { ...@@ -114,6 +114,7 @@ test('address verification flow', async({ mount, page }) => {
await page.getByRole('button', { name: /continue/i }).click(); await page.getByRole('button', { name: /continue/i }).click();
// fill second step // fill second step
await page.getByText('Sign manually').click();
const signatureInput = page.getByLabel(/signature hash/i); const signatureInput = page.getByLabel(/signature hash/i);
await signatureInput.fill(mocks.SIGNATURE); await signatureInput.fill(mocks.SIGNATURE);
await page.getByRole('button', { name: /verify/i }).click(); await page.getByRole('button', { name: /verify/i }).click();
......
import { useColorMode } from '@chakra-ui/react'; import { useColorMode } from '@chakra-ui/react';
import { jsonRpcProvider } from '@wagmi/core/providers/jsonRpc'; import { createWeb3Modal, useWeb3ModalTheme } from '@web3modal/wagmi/react';
import { createWeb3Modal, useWeb3ModalTheme, defaultWagmiConfig } from '@web3modal/wagmi/react';
import React from 'react'; import React from 'react';
import { configureChains, WagmiConfig } from 'wagmi'; import { WagmiProvider } from 'wagmi';
import config from 'configs/app'; import config from 'configs/app';
import currentChain from 'lib/web3/currentChain'; import wagmiConfig from 'lib/web3/wagmiConfig';
import colors from 'theme/foundations/colors'; import colors from 'theme/foundations/colors';
import { BODY_TYPEFACE } from 'theme/foundations/typography'; import { BODY_TYPEFACE } from 'theme/foundations/typography';
import zIndices from 'theme/foundations/zIndices'; import zIndices from 'theme/foundations/zIndices';
const feature = config.features.blockchainInteraction; const feature = config.features.blockchainInteraction;
const getConfig = () => { const init = () => {
try { try {
if (!feature.isEnabled) { if (!wagmiConfig || !feature.isEnabled) {
throw new Error(); return;
} }
const { chains } = configureChains(
[ currentChain ],
[
jsonRpcProvider({
rpc: () => ({
http: config.chain.rpcUrl || '',
}),
}),
],
);
const wagmiConfig = defaultWagmiConfig({
chains,
projectId: feature.walletConnect.projectId,
enableEmail: true,
});
createWeb3Modal({ createWeb3Modal({
wagmiConfig, wagmiConfig,
projectId: feature.walletConnect.projectId, projectId: feature.walletConnect.projectId,
chains,
themeVariables: { themeVariables: {
'--w3m-font-family': `${ BODY_TYPEFACE }, sans-serif`, '--w3m-font-family': `${ BODY_TYPEFACE }, sans-serif`,
'--w3m-accent': colors.blue[600], '--w3m-accent': colors.blue[600],
'--w3m-border-radius-master': '2px', '--w3m-border-radius-master': '2px',
'--w3m-z-index': zIndices.modal, '--w3m-z-index': zIndices.modal,
}, },
featuredWalletIds: [],
}); });
} catch (error) {}
return { wagmiConfig };
} catch (error) {
return { };
}
}; };
const { wagmiConfig } = getConfig(); init();
interface Props { interface Props {
children: React.ReactNode; children: React.ReactNode;
...@@ -78,9 +56,9 @@ const Provider = ({ children, fallback }: Props) => { ...@@ -78,9 +56,9 @@ const Provider = ({ children, fallback }: Props) => {
} }
return ( return (
<WagmiConfig config={ wagmiConfig }> <WagmiProvider config={ wagmiConfig }>
{ children } { children }
</WagmiConfig> </WagmiProvider>
); );
}; };
......
import { Tag } from '@chakra-ui/react'; import { Tag } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { UserOpSponsorType } from 'types/api/userOps'; import type { UserOpSponsorType as TUserOpSponsorType } from 'types/api/userOps';
type Props = { type Props = {
sponsorType: UserOpSponsorType; sponsorType: TUserOpSponsorType;
} }
const UserOpSponsorType = ({ sponsorType }: Props) => { const UserOpSponsorType = ({ sponsorType }: Props) => {
......
import { test as base, expect } from '@playwright/experimental-ct-react'; import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react'; import React from 'react';
import type { WindowProvider } from 'wagmi';
import type { WalletProvider } from 'types/web3';
import { buildExternalAssetFilePath } from 'configs/app/utils'; import { buildExternalAssetFilePath } from 'configs/app/utils';
import { FOOTER_LINKS } from 'mocks/config/footerLinks'; import { FOOTER_LINKS } from 'mocks/config/footerLinks';
...@@ -36,7 +37,7 @@ base.describe('with custom links, max cols', () => { ...@@ -36,7 +37,7 @@ base.describe('with custom links, max cols', () => {
window.ethereum = { window.ethereum = {
isMetaMask: true, isMetaMask: true,
_events: {}, _events: {},
} as WindowProvider; } as WalletProvider;
}); });
await page.route(INDEXING_ALERT_API_URL, (route) => route.fulfill({ await page.route(INDEXING_ALERT_API_URL, (route) => route.fulfill({
...@@ -95,7 +96,7 @@ base.describe('without custom links', () => { ...@@ -95,7 +96,7 @@ base.describe('without custom links', () => {
window.ethereum = { window.ethereum = {
isMetaMask: true, isMetaMask: true,
_events: {}, _events: {},
} as WindowProvider; } as WalletProvider;
}); });
await page.route(BACKEND_VERSION_API_URL, (route) => { await page.route(BACKEND_VERSION_API_URL, (route) => {
return route.fulfill({ return route.fulfill({
...@@ -118,7 +119,7 @@ base.describe('without custom links', () => { ...@@ -118,7 +119,7 @@ base.describe('without custom links', () => {
await page.evaluate(() => { await page.evaluate(() => {
window.ethereum = { window.ethereum = {
providers: [ { isMetaMask: true, _events: {} } ], providers: [ { isMetaMask: true, _events: {} } ],
} as WindowProvider; } as WalletProvider;
}); });
await page.route(INDEXING_ALERT_API_URL, (route) => route.fulfill({ await page.route(INDEXING_ALERT_API_URL, (route) => route.fulfill({
......
import { useWeb3Modal, useWeb3ModalState } from '@web3modal/wagmi/react'; import { useWeb3Modal, useWeb3ModalState } from '@web3modal/wagmi/react';
import React from 'react'; import React from 'react';
import { useAccount, useDisconnect } from 'wagmi'; import { useAccount, useDisconnect, useAccountEffect } from 'wagmi';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
...@@ -38,7 +38,9 @@ export default function useWallet({ source }: Params) { ...@@ -38,7 +38,9 @@ export default function useWallet({ source }: Params) {
disconnect(); disconnect();
}, [ disconnect ]); }, [ disconnect ]);
const { address, isDisconnected } = useAccount({ onConnect: handleAccountConnected }); useAccountEffect({ onConnect: handleAccountConnected });
const { address, isDisconnected } = useAccount();
const isWalletConnected = isClientLoaded && !isDisconnected && address !== undefined; const isWalletConnected = isClientLoaded && !isDisconnected && address !== undefined;
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment