Commit 8d9b3f49 authored by tom's avatar tom

update wagmi and web3modal versions

parent 07d53241
import type { ExternalProvider } from 'types/client/wallets';
import type { WindowProvider } from 'wagmi';
type CPreferences = {
zone: string;
......@@ -8,9 +8,7 @@ type CPreferences = {
declare global {
export interface Window {
ethereum?: {
providers?: Array<ExternalProvider>;
};
ethereum?: WindowProvider;
coinzilla_display: Array<CPreferences>;
ga?: {
getAll: () => Array<{ get: (prop: string) => string }>;
......
......@@ -10,8 +10,7 @@ export function walletConnect(): CspDev.DirectiveDescriptor {
return {
'connect-src': [
'*.walletconnect.com',
'wss://*.bridge.walletconnect.org',
'wss://www.walletlink.org',
'wss://relay.walletconnect.com',
],
'img-src': [
'*.walletconnect.com',
......
import React from 'react';
import type { WindowProvider } from 'wagmi';
import type { ExternalProvider } from 'types/client/wallets';
import 'wagmi/window';
import appConfig from 'configs/app/config';
export default function useProvider() {
const [ provider, setProvider ] = React.useState<ExternalProvider>();
const [ provider, setProvider ] = React.useState<WindowProvider>();
React.useEffect(() => {
if (!('ethereum' in window)) {
......
......@@ -28,6 +28,7 @@ const moduleExports = withTM({
use: [ '@svgr/webpack' ],
},
);
config.resolve.fallback = { fs: false, net: false, tls: false };
return config;
},
......
import { ChakraProvider } from '@chakra-ui/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { providers } from 'ethers';
import { w3mProvider } from '@web3modal/ethereum';
import React from 'react';
import { createClient, WagmiConfig } from 'wagmi';
import { createWalletClient, custom } from 'viem';
import { configureChains, createConfig, WagmiConfig } from 'wagmi';
import { mainnet } from 'wagmi/chains';
import { MockConnector } from 'wagmi/connectors/mock';
......@@ -28,25 +29,27 @@ const defaultAppContext = {
};
// >>> Web3 stuff
const provider = new providers.JsonRpcProvider(
'http://localhost:8545',
{
name: 'POA',
chainId: 99,
},
);
const connector = new MockConnector({
chains: [ mainnet ],
options: {
signer: provider.getSigner(),
chainId: mainnet.id,
walletClient: createWalletClient({
chain: mainnet,
transport: custom(window.ethereum!),
}),
},
});
const wagmiClient = createClient({
autoConnect: true,
const { publicClient } = configureChains(
[ mainnet ],
[
w3mProvider({ projectId: '' }),
],
);
const wagmiConfig = createConfig({
autoConnect: false,
connectors: [ connector ],
provider,
publicClient,
});
// <<<<
......@@ -65,7 +68,7 @@ const TestApp = ({ children, withSocket, appContext = defaultAppContext }: Props
<QueryClientProvider client={ queryClient }>
<SocketProvider url={ withSocket ? `ws://localhost:${ PORT }` : undefined }>
<AppContextProvider { ...appContext }>
<WagmiConfig client={ wagmiClient }>
<WagmiConfig config={ wagmiConfig }>
{ children }
</WagmiConfig>
</AppContextProvider>
......
import type { Abi } from 'abitype';
// import type { ArrayElement } from 'types/utils';
export type SmartContractMethodArgType = 'address' | 'uint256' | 'bool' | 'string' | 'bytes' | 'bytes32';
export type SmartContractMethodStateMutability = 'view' | 'nonpayable' | 'payable';
......@@ -80,15 +82,19 @@ export interface SmartContractWriteReceive {
}
export type SmartContractWriteMethod = SmartContractMethodBase | SmartContractWriteFallback | SmartContractWriteReceive;
// export type SmartContractWriteMethod = ArrayElement<Abi> & {name?: string};
// export type SmartContractReadMethod = ArrayElement<Abi> & {name?: string};
export type SmartContractMethod = SmartContractReadMethod | SmartContractWriteMethod;
// export type SmartContractMethodInput = ArrayElement<Abi> & {name?: string};
export interface SmartContractMethodInput {
internalType?: SmartContractMethodArgType;
name: string;
type: SmartContractMethodArgType;
}
// export type SmartContractMethodOutput = ArrayElement<Abi> & {name?: string};
export interface SmartContractMethodOutput extends SmartContractMethodInput {
value?: string;
}
......
import type { providers } from 'ethers';
export type WalletType = 'metamask' | 'coinbase';
export interface WalletInfo {
name: string;
icon: React.ElementType;
}
export interface ExternalProvider extends providers.ExternalProvider {
isCoinbaseWallet?: boolean;
// have to patch ethers here, since params could be not only an array
// eslint-disable-next-line @typescript-eslint/no-explicit-any
request?: (request: { method: string; params?: any }) => Promise<any>;
}
export type ArrayElement<ArrayType extends Array<unknown>> =
ArrayType extends Array<(infer ElementType)> ? ElementType : never;
export type ArrayElement<ArrType> = ArrType extends ReadonlyArray<infer ElementType>
? ElementType
: never;
export type ExcludeNull<T> = T extends null ? never : T;
......
import { test, expect } from '@playwright/experimental-ct-react';
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
import type { WindowProvider } from 'wagmi';
import type { Address } from 'types/api/address';
......@@ -74,7 +75,7 @@ test('token', async({ mount, page }) => {
await page.evaluate(() => {
window.ethereum = {
providers: [ { isMetaMask: true } ],
};
}as WindowProvider;
});
const component = await mount(
......
......@@ -25,7 +25,7 @@ interface Props<T extends SmartContractMethod> {
isWrite?: boolean;
}
const getFieldName = (name: string, index: number): string => name || String(index);
const getFieldName = (name: string | undefined, index: number): string => name || String(index);
const sortFields = (data: Array<SmartContractMethodInput>) => ([ a ]: [string, string], [ b ]: [string, string]): 1 | -1 | 0 => {
const fieldNames = data.map(({ name }, index) => getFieldName(name, index));
......@@ -69,12 +69,13 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit,
const inputs = React.useMemo(() => {
return [
...('inputs' in data ? data.inputs : []),
...(data.stateMutability === 'payable' ? [ {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...('inputs' in data ? data.inputs as Array<any> : []), // todo_tom fix type
...('stateMutability' in data && data.stateMutability === 'payable' ? [ {
name: 'value',
type: appConfig.network.currency.symbol,
internalType: appConfig.network.currency.symbol,
} as SmartContractMethodInput ] : []),
} ] : []),
];
}, [ data ]);
......
import React from 'react';
import { useAccount, useSigner, useNetwork, useSwitchNetwork } from 'wagmi';
import { useAccount, useWalletClient, useNetwork, useSwitchNetwork } from 'wagmi';
import type { SmartContractWriteMethod } from 'types/api/contract';
......@@ -24,7 +24,7 @@ interface Props {
}
const ContractWrite = ({ addressHash, isProxy, isCustomAbi }: Props) => {
const { data: signer } = useSigner();
const { data: walletClient } = useWalletClient();
const { isConnected } = useAccount();
const { chain } = useNetwork();
const { switchNetworkAsync } = useSwitchNetwork();
......@@ -67,24 +67,28 @@ const ContractWrite = ({ addressHash, isProxy, isCustomAbi }: Props) => {
if (item.type === 'receive') {
const value = args[0] ? getNativeCoinValue(args[0]) : '0';
const result = await signer?.sendTransaction({
to: addressHash,
value,
const hash = await walletClient?.sendTransaction({
to: addressHash as `0x${ string }` | undefined,
value: BigInt(value), // todo_tom simplify this
});
return { hash: result?.hash as string };
return { hash };
}
const _args = item.stateMutability === 'payable' ? args.slice(0, -1) : args;
const value = item.stateMutability === 'payable' ? getNativeCoinValue(args[args.length - 1]) : undefined;
const _args = 'stateMutability' in item && item.stateMutability === 'payable' ? args.slice(0, -1) : args;
const value = 'stateMutability' in item && item.stateMutability === 'payable' ? getNativeCoinValue(args[args.length - 1]) : undefined;
const methodName = item.type === 'fallback' ? 'fallback' : item.name;
const result = await _contract[methodName](..._args, {
if (!methodName) {
throw new Error('Method name is not defined');
}
const hash = await _contract.write[methodName](..._args, {
gasLimit: 100_000,
value,
});
return { hash: result.hash as string };
}, [ _contract, addressHash, chain, isConnected, signer, switchNetworkAsync ]);
return { hash };
}, [ _contract, addressHash, chain, isConnected, walletClient, switchNetworkAsync ]);
const renderContent = React.useCallback((item: SmartContractWriteMethod, index: number, id: number) => {
return (
......
......@@ -12,7 +12,7 @@ test('loading', async({ mount }) => {
error: null,
},
result: {
hash: '0x363574E6C5C71c343d7348093D84320c76d5Dd29',
hash: '0x363574E6C5C71c343d7348093D84320c76d5Dd29' as `0x${ string }`,
},
onSettle: () => {},
};
......@@ -33,7 +33,7 @@ test('success', async({ mount }) => {
error: null,
},
result: {
hash: '0x363574E6C5C71c343d7348093D84320c76d5Dd29',
hash: '0x363574E6C5C71c343d7348093D84320c76d5Dd29' as `0x${ string }`,
},
onSettle: () => {},
};
......@@ -57,7 +57,7 @@ test('error +@mobile', async({ mount }) => {
} as Error,
},
result: {
hash: '0x363574E6C5C71c343d7348093D84320c76d5Dd29',
hash: '0x363574E6C5C71c343d7348093D84320c76d5Dd29' as `0x${ string }`,
},
onSettle: () => {},
};
......
import { useQueryClient } from '@tanstack/react-query';
import type { Contract } from 'ethers';
import type { Abi } from 'abitype';
import React from 'react';
import { useContract, useProvider, useSigner } from 'wagmi';
import type { WalletClient } from 'wagmi';
import { useWalletClient } from 'wagmi';
import type { GetContractResult } from 'wagmi/actions';
import { getContract } from 'wagmi/actions';
import type { Address } from 'types/api/address';
......@@ -13,20 +16,19 @@ type ProviderProps = {
}
type TContractContext = {
contract: Contract | null;
proxy: Contract | null;
custom: Contract | null;
contract: GetContractResult<Abi, WalletClient> | undefined;
proxy: GetContractResult<Abi, WalletClient> | undefined;
custom: GetContractResult<Abi, WalletClient> | undefined;
};
const ContractContext = React.createContext<TContractContext>({
contract: null,
proxy: null,
custom: null,
contract: undefined,
proxy: undefined,
custom: undefined,
});
export function ContractContextProvider({ addressHash, children }: ProviderProps) {
const provider = useProvider();
const { data: signer } = useSigner();
const { data: walletClient } = useWalletClient();
const queryClient = useQueryClient();
const { data: contractInfo } = useApiQuery('contract', {
......@@ -58,27 +60,30 @@ export function ContractContextProvider({ addressHash, children }: ProviderProps
},
});
const contract = useContract({
address: addressHash,
const contract = addressHash && contractInfo?.abi ? getContract({
address: addressHash as `0x${ string }`,
abi: contractInfo?.abi ?? undefined,
signerOrProvider: signer ?? provider,
});
const proxy = useContract({
address: addressInfo?.implementation_address ?? undefined,
abi: proxyInfo?.abi ?? undefined,
signerOrProvider: signer ?? provider,
});
const custom = useContract({
address: addressHash,
abi: customInfo ?? undefined,
signerOrProvider: signer ?? provider,
});
walletClient: walletClient ?? undefined,
}) : undefined;
const proxy = addressInfo?.implementation_address && proxyInfo?.abi ? getContract({
address: addressInfo?.implementation_address as `0x${ string }`,
abi: proxyInfo?.abi,
walletClient: walletClient ?? undefined,
}) : undefined;
const custom = addressHash && customInfo ? getContract({
address: addressHash as `0x${ string }`,
abi: customInfo,
walletClient: walletClient ?? undefined,
}) : undefined;
const value = React.useMemo(() => ({
contract,
proxy,
custom,
}), [ contract, proxy, custom ]);
// todo_tom fix this
} as TContractContext), [ contract, proxy, custom ]);
return (
<ContractContext.Provider value={ value }>
......
......@@ -6,7 +6,7 @@ export type MethodFormFields = Record<string, string>;
export type ContractMethodReadResult = SmartContractQueryMethodRead | ResourceError;
export type ContractMethodWriteResult = Error | { hash: string } | undefined;
export type ContractMethodWriteResult = Error | { hash: `0x${ string }` | undefined } | undefined;
export type ContractMethodCallResult<T extends SmartContractMethod> =
T extends { method_id: string } ? ContractMethodReadResult : ContractMethodWriteResult;
......@@ -30,7 +30,9 @@ const NetworkAddToWallet = ({ className }: Props) => {
rpcUrls: [ appConfig.network.rpcUrl ],
blockExplorerUrls: [ appConfig.baseUrl ],
} ],
};
// in wagmi types for wallet_addEthereumChain method is not provided
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any;
await provider?.request?.(config);
toast({
position: 'top-right',
......
import { useColorModeValue, useToken } from '@chakra-ui/react';
import {
EthereumClient,
modalConnectors,
walletConnectProvider,
} from '@web3modal/ethereum';
import { EthereumClient, w3mConnectors, w3mProvider } from '@web3modal/ethereum';
import { Web3Modal } from '@web3modal/react';
import React from 'react';
import type { Chain } from 'wagmi';
import { configureChains, createClient, WagmiConfig } from 'wagmi';
import { configureChains, createConfig, WagmiConfig } from 'wagmi';
import appConfig from 'configs/app/config';
const { wagmiClient, ethereumClient } = (() => {
const getConfig = () => {
try {
if (!appConfig.walletConnect.projectId) {
throw new Error('WalletConnect Project ID is not set');
}
const currentChain: Chain = {
id: Number(appConfig.network.id),
name: appConfig.network.name || '',
......@@ -23,6 +23,9 @@ const { wagmiClient, ethereumClient } = (() => {
symbol: appConfig.network.currency.symbol || '',
},
rpcUrls: {
'public': {
http: [ appConfig.network.rpcUrl || '' ],
},
'default': {
http: [ appConfig.network.rpcUrl || '' ],
},
......@@ -37,46 +40,50 @@ const { wagmiClient, ethereumClient } = (() => {
const chains = [ currentChain ];
const { provider } = configureChains(chains, [
walletConnectProvider({ projectId: appConfig.walletConnect.projectId || '' }),
const { publicClient } = configureChains(chains, [
w3mProvider({ projectId: appConfig.walletConnect.projectId || '' }),
]);
const wagmiClient = createClient({
const wagmiConfig = createConfig({
autoConnect: true,
connectors: modalConnectors({ appName: 'web3Modal', chains }),
provider,
connectors: w3mConnectors({ projectId: appConfig.walletConnect.projectId, chains, version: 2 }),
publicClient,
});
const ethereumClient = new EthereumClient(wagmiClient, chains);
const ethereumClient = new EthereumClient(wagmiConfig, chains);
return { wagmiClient, ethereumClient };
return { wagmiConfig, ethereumClient };
} catch (error) {
return { wagmiClient: undefined, ethereumClient: undefined };
return { wagmiConfig: undefined, ethereumClient: undefined };
}
})();
};
interface Props {
children: React.ReactNode;
fallback?: JSX.Element | (() => JSX.Element);
}
const { wagmiConfig, ethereumClient } = getConfig();
const Web3ModalProvider = ({ children, fallback }: Props) => {
const modalZIndex = useToken<string>('zIndices', 'modal');
const web3ModalTheme = useColorModeValue('light', 'dark');
if (!wagmiClient || !ethereumClient) {
if (!wagmiConfig || !ethereumClient || !appConfig.walletConnect.projectId) {
return typeof fallback === 'function' ? fallback() : (fallback || null);
}
return (
<WagmiConfig client={ wagmiClient }>
<>
<WagmiConfig config={ wagmiConfig }>
{ children }
</WagmiConfig>
<Web3Modal
projectId={ appConfig.walletConnect.projectId }
ethereumClient={ ethereumClient }
themeZIndex={ Number(modalZIndex) }
themeMode={ web3ModalTheme }
themeBackground="themeColor"
themeVariables={{
'--w3m-z-index': modalZIndex,
}}
/>
</WagmiConfig>
</>
);
};
......
......@@ -26,7 +26,7 @@ const AddressAddToWallet = ({ className, token, isLoading }: Props) => {
type: 'ERC20', // Initially only supports ERC20, but eventually more!
options: {
address: token.address,
symbol: token.symbol,
symbol: token.symbol || '',
decimals: Number(token.decimals) || 18,
// TODO: add token image when we have it in API
// image: ''
......
import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import type { WindowProvider } from 'wagmi';
import { FOOTER_LINKS } from 'mocks/config/footerLinks';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
......@@ -64,7 +65,7 @@ base.describe('without custom links', () => {
await page.evaluate(() => {
window.ethereum = {
providers: [ { isMetaMask: true } ],
};
} as WindowProvider;
});
await mount(
......
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