Skip to content

Commit

Permalink
Upgrade wagmi to v1 (#424)
Browse files Browse the repository at this point in the history
* Upgrade wagmi to v1

* Show warning message when we detect that the storage is full

* Add TextEncoder to jest environment

* Bump viem to latest, and upgrade Typescript to v5
  • Loading branch information
mcoetzee authored Oct 6, 2023
1 parent 5277a85 commit c3b8cb4
Show file tree
Hide file tree
Showing 15 changed files with 632 additions and 425 deletions.
3 changes: 2 additions & 1 deletion config-overrides.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,11 @@ module.exports = {

config.transformIgnorePatterns = [
// The wagmi package ships with untranspiled import statements, so we tell Jest not to ignore them
'/node_modules/(?!wagmi|@wagmi).+\\.(js|jsx|mjs|cjs|ts|tsx)$',
'/node_modules/(?!wagmi|@wagmi|@adraffy/ens-normalize).+\\.(js|jsx|mjs|cjs|ts|tsx)$',
defaultIgnorePatterns[1],
];

config.setupFiles = ['<rootDir>/setup-jest.js'];
return config;
},
};
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@
},
"dependencies": {
"@sentry/react": "^6.19.7",
"@web3modal/ethereum": "^2.4.1",
"@web3modal/react": "^2.4.1",
"@web3modal/ethereum": "^2.7.1",
"@web3modal/react": "^2.7.1",
"classnames": "^2.3.2",
"date-fns": "^2.29.3",
"ethers": "^5.7.2",
Expand All @@ -70,7 +70,8 @@
"react-number-format": "^4.9.4",
"react-router-dom": "^5.3.4",
"react-toastify": "^7.0.4",
"wagmi": "^0.12.13"
"viem": "^1.14.0",
"wagmi": "^1.4.3"
},
"devDependencies": {
"@api3/promise-utils": "^0.4.0",
Expand Down Expand Up @@ -113,16 +114,15 @@
"start-server-and-test": "^1.14.0",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"ts-node": "^10.4.0",
"ts-node": "^10.9.1",
"typechain": "^8.1.1",
"typescript": "^4.5.5",
"typescript-plugin-css-modules": "^4.1.1",
"typescript": "5.1.6",
"typescript-plugin-css-modules": "^5.0.1",
"url": "^0.11.0"
},
"resolutions": {
"@types/jest": "^27.5.2",
"@types/react": "^18.0.26",
"@walletconnect/ethereum-provider": "^2.7.5",
"jest-regex-util": "^27.5.1",
"jest-watcher": "^27.5.1",
"jest-worker": "^27.5.1"
Expand Down
4 changes: 4 additions & 0 deletions setup-jest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { TextEncoder, TextDecoder } from 'util';

global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;
2 changes: 1 addition & 1 deletion src/__test_utils__/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const wagmiClient = createMockClient();
export const render = (ui: ReactElement, options?: RenderOptions): RenderResult => {
return baseRender(
<BrowserRouter>
<WagmiConfig client={wagmiClient}>{ui}</WagmiConfig>
<WagmiConfig config={wagmiClient}>{ui}</WagmiConfig>
</BrowserRouter>,
options
);
Expand Down
60 changes: 22 additions & 38 deletions src/__test_utils__/mock-wagmi-client.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,35 @@
/**
* Note: https://github.com/tmm/testing-wagmi/tree/main/test was used as the starting point for this code
*/
import { createClient, CreateClientConfig } from 'wagmi';
import { createConfig, CreateConfigParameters, WalletClient } from 'wagmi';
import { MockConnector } from 'wagmi/dist/connectors/mock';
import { hardhat } from 'wagmi/chains';
import { providers, Wallet } from 'ethers';
import { createPublicClient, createWalletClient, http } from 'viem';

type Config = Partial<CreateClientConfig> & { signer?: WalletSigner };
type Config = Partial<CreateConfigParameters> & { walletClient?: WalletClient };

export function createMockClient({ signer = getSigners()[0]!, ...config }: Config = {}) {
return createClient({
connectors: [new MockConnector({ options: { signer } })],
provider: () => getHardhatProvider(),
export function createMockClient({ walletClient = getMockWalletClient(), ...config }: Config = {}) {
return createConfig({
connectors: [new MockConnector({ options: { walletClient } })],
publicClient: () => getPublicClient(),
...config,
});
}

function getHardhatProvider() {
const rpcUrl = hardhat.rpcUrls.default.http[0];
return new providers.StaticJsonRpcProvider(rpcUrl, {
chainId: hardhat.id,
name: hardhat.name,
ensAddress: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
export const getMockWalletClient = () =>
// Default Hardhat account
createWalletClient({
transport: http(hardhat.rpcUrls.default.http[0]),
chain: hardhat,
account: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
key: '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
pollingInterval: 100,
});
}

function getSigners() {
const provider = getHardhatProvider();
return accounts.map((acc) => new WalletSigner(acc.privateKey, provider));
}

class WalletSigner extends Wallet {
connectUnchecked(): providers.JsonRpcSigner {
return (this.provider as providers.StaticJsonRpcProvider).getUncheckedSigner(this.address);
}
}

// Default Hardhat accounts
const accounts = [
{
// Account #0: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
privateKey: '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
balance: '10000000000000000000000',
},
{
// Account #1: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
privateKey: '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d',
balance: '10000000000000000000000',
},
];
export const getPublicClient = () => {
return createPublicClient({
transport: http(hardhat.rpcUrls.default.http[0]),
chain: hardhat,
pollingInterval: 100,
});
};
30 changes: 29 additions & 1 deletion src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ import NotFoundPage from './pages/not-found';
import ProposalDetailsPage from './pages/proposal-commons/proposal-details';
import Proposals from './pages/proposals';
import Vesting from './pages/vesting';
import StorageFullNotification from './components/notifications/storage-full-notification';
import { wagmiClient, ethereumClient, projectId } from './wallet-connect';
import './styles/variables.module.scss';
import { notifications } from './components/notifications';

const ErrorBoundary: FallbackRender = (props) => {
const { error } = props;
Expand Down Expand Up @@ -77,7 +79,7 @@ const AppContent = () => {

const App = () => {
return (
<WagmiConfig client={wagmiClient}>
<WagmiConfig config={wagmiClient}>
<ChainDataContextProvider>
<HelmetProvider>
{/* Helmet children can be overridden in components lower down the tree */}
Expand All @@ -100,3 +102,29 @@ const App = () => {
};

export default App;

/**
* We've picked up some local storage issues through the use of the web3modal
* (see https://github.com/WalletConnect/web3modal/issues/1345). The issue has apparently been fixed, but as a precaution,
* we patch the localStorage.setItem function to show a warning message when it fails to store something.
*/
const setStorageItem = window.localStorage.setItem.bind(window.localStorage);
window.localStorage.setItem = (key, value) => {
try {
setStorageItem(key, value);
} catch (e) {
notifications.warning(
{ message: <StorageFullNotification /> },
{
toastId: 'storage-warning',
bodyClassName: 'cursor-auto',
closeButton: false,
closeOnClick: false,
autoClose: false,
draggable: false,
}
);

throw e;
}
};
51 changes: 51 additions & 0 deletions src/chain-data/adapters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Source: https://wagmi.sh/react/ethers-adapters
*/
import { type PublicClient, usePublicClient, type WalletClient, useWalletClient } from 'wagmi';
import { providers } from 'ethers';
import { type HttpTransport } from 'viem';
import { useMemo } from 'react';

export function publicClientToProvider(publicClient: PublicClient) {
const { chain, transport } = publicClient;
const network = {
chainId: chain.id,
name: chain.name,
ensAddress: chain.contracts?.ensRegistry?.address,
};
if (transport.type === 'fallback')
return new providers.FallbackProvider(
(transport.transports as ReturnType<HttpTransport>[]).map(
({ value }) => new providers.JsonRpcProvider(value?.url, network)
)
);
return new providers.JsonRpcProvider(transport.url, network);
}

/**
* Hook to convert a viem Public Client to an ethers.js Provider.
*/
export function useEthersProvider({ chainId }: { chainId?: number } = {}) {
const publicClient = usePublicClient({ chainId });
return useMemo(() => publicClientToProvider(publicClient), [publicClient]);
}

export function walletClientToSigner(walletClient: WalletClient) {
const { account, chain, transport } = walletClient;
const network = {
chainId: chain.id,
name: chain.name,
ensAddress: chain.contracts?.ensRegistry?.address,
};
const provider = new providers.Web3Provider(transport, network);
const signer = provider.getSigner(account.address);
return signer;
}

/**
* Hook to convert a viem Wallet Client to an ethers.js Signer.
*/
export function useEthersSigner({ chainId }: { chainId?: number } = {}) {
const { data: walletClient } = useWalletClient({ chainId });
return useMemo(() => (walletClient ? walletClientToSigner(walletClient) : undefined), [walletClient]);
}
20 changes: 15 additions & 5 deletions src/chain-data/context.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import { createContext, useState, useMemo, useContext, ReactNode } from 'react';
import { initialSettableChainData, initialChainData, SettableChainData } from './state';
import { useAccount, useNetwork, useProvider, useSigner } from 'wagmi';
import { useAccount, useNetwork } from 'wagmi';
import { getDaoAddresses, updateNetworkName } from '../contracts';
import { useEthersProvider, useEthersSigner } from './adapters';
import { ethers } from 'ethers';

export const ChainDataContext = createContext(initialSettableChainData);

const ProviderSignerContext = createContext<null | {
provider: ethers.providers.Provider;
signer?: ethers.Signer;
}>(null);

const ChainDataContextProvider = (props: { children: ReactNode }) => {
const [chainData, setChainData] = useState(initialChainData);
// We call these adapter hooks here and provide the provider and signer with context, so that we use a
// single instance of each across the app
const provider = useEthersProvider();
const signer = useEthersSigner();

const loggableSetChainData = useMemo(() => {
const setter: SettableChainData['setChainData'] = (reason, dataOrCallback) => {
Expand All @@ -30,7 +41,7 @@ const ChainDataContextProvider = (props: { children: ReactNode }) => {

return (
<ChainDataContext.Provider value={{ ...chainData, setChainData: loggableSetChainData }}>
{props.children}
<ProviderSignerContext.Provider value={{ provider, signer }}>{props.children}</ProviderSignerContext.Provider>
</ChainDataContext.Provider>
);
};
Expand All @@ -39,13 +50,12 @@ export default ChainDataContextProvider;

export const useChainData = () => {
const data = useContext(ChainDataContext);
const { provider, signer } = useContext(ProviderSignerContext) || {};
const { chain } = useNetwork();
const provider = useProvider();
const { data: signer } = useSigner();
const { isConnected, address } = useAccount();

// Note: The signer is briefly undefined after connecting or after switching networks.
if (isConnected && address && signer) {
if (isConnected && address && provider && signer) {
const networkName = updateNetworkName(chain?.network || '');
return {
...data,
Expand Down
5 changes: 3 additions & 2 deletions src/components/notifications/notifications.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ReactNode } from 'react';
import throttle from 'lodash/throttle';
import classNames from 'classnames';
import { toast, Slide, ToastOptions } from 'react-toastify';
Expand All @@ -23,7 +24,7 @@ export const CloseButton = ({ closeToast }: CloseButtonProps) => (
);

interface ToastProps {
message: string;
message: ReactNode;
url?: string;
}

Expand All @@ -41,7 +42,7 @@ const CustomToast = ({ message, type, url }: ToastPropsWithType) => {
<div className={classNames(styles.notificationBody, { [styles.url]: url })}>
<img src={`/${type}.svg`} alt={`${type} icon`} />
<div className={styles.notificationContent}>
<p>{message}</p>
<div>{message}</div>
{url && (
<div className={styles.notificationUrl}>
<NotificationLinkButton href={url}>View transaction</NotificationLinkButton>
Expand Down
36 changes: 36 additions & 0 deletions src/components/notifications/storage-full-notification.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
@import '../../styles/variables.module.scss';

.buttonRow {
display: flex;
flex-direction: column-reverse;
justify-content: flex-end;
gap: 20px;
margin-top: 20px;

@media (min-width: $min-sm) {
flex-direction: row;
}
}

.cancelButton {
cursor: pointer;
border: none;
background: none;
font-weight: 400;
}

.clearButton {
cursor: pointer;
background-color: #194aff;
color: $primary-color;
min-width: 15ch;
padding: 12px 10px;
font-weight: 500;
border-radius: 4px;
border: none;
white-space: nowrap;

&:disabled {
opacity: 0.8;
}
}
34 changes: 34 additions & 0 deletions src/components/notifications/storage-full-notification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useState } from 'react';
import { closeAll } from './notifications';
import { ERROR_REPORTING_CONSENT_KEY_NAME } from '../../utils';
import styles from './storage-full-notification.module.scss';

export default function StorageFullNotification() {
const [busy, setBusy] = useState(false);

return (
<div>
We have detected that your local storage is full. Would you like to clear it and refresh the page?
<div className={styles.buttonRow}>
<button onClick={() => closeAll()} className={styles.cancelButton}>
Cancel
</button>
<button
className={styles.clearButton}
disabled={busy}
onClick={() => {
setBusy(true);
const errorReportingValue = window.localStorage.getItem(ERROR_REPORTING_CONSENT_KEY_NAME);
window.localStorage.clear();
if (errorReportingValue) {
window.localStorage.setItem(ERROR_REPORTING_CONSENT_KEY_NAME, errorReportingValue);
}
window.location.reload();
}}
>
{busy ? 'Clearing...' : 'Clear Storage'}
</button>
</div>
</div>
);
}
4 changes: 4 additions & 0 deletions src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ code {
position: relative;
z-index: 100;
}

.cursor-auto {
cursor: auto;
}
Loading

0 comments on commit c3b8cb4

Please sign in to comment.