From af274d33faf77bbdaa48da4173bb73fb103fd230 Mon Sep 17 00:00:00 2001
From: ECWireless
Date: Thu, 30 May 2024 07:53:08 -0600
Subject: [PATCH 1/2] Refactor DelegationButton
---
.../src/components/ConnectWalletModal.tsx | 62 +----------
.../src/components/DelegationButton.tsx | 102 ++++++++++++++++++
packages/client/src/contexts/Web3Provider.tsx | 5 +-
packages/client/src/lib/web3/constants.ts | 27 +++++
packages/client/src/lib/web3/helpers.ts | 27 +++++
packages/client/src/lib/web3/index.ts | 2 +
packages/client/src/utils/theme.ts | 10 +-
7 files changed, 172 insertions(+), 63 deletions(-)
create mode 100644 packages/client/src/components/DelegationButton.tsx
create mode 100644 packages/client/src/lib/web3/helpers.ts
create mode 100644 packages/client/src/lib/web3/index.ts
diff --git a/packages/client/src/components/ConnectWalletModal.tsx b/packages/client/src/components/ConnectWalletModal.tsx
index 41142e1fc..93a15ddba 100644
--- a/packages/client/src/components/ConnectWalletModal.tsx
+++ b/packages/client/src/components/ConnectWalletModal.tsx
@@ -9,14 +9,12 @@ import {
Text,
VStack,
} from '@chakra-ui/react';
-import { useEffect, useMemo } from 'react';
-import type { Account, Chain, Hex, Transport, WalletClient } from 'viem';
-import { useAccount, useSwitchChain, useWalletClient } from 'wagmi';
+import { useMemo } from 'react';
+import { useAccount, useWalletClient } from 'wagmi';
import { useMUD } from '../contexts/MUDContext';
-import { useDelegation } from '../hooks/useDelegation';
-import { type Burner, createBurner } from '../lib/mud/createBurner';
import { ConnectWalletButton } from './ConnectWalletButton';
+import { DelegationButton } from './DelegationButton';
export const ConnectWalletModal = ({
isOpen,
@@ -36,6 +34,7 @@ export const ConnectWalletModal = ({
@@ -68,6 +67,7 @@ export const ConnectWalletModal = ({
@@ -102,55 +102,3 @@ export const ConnectWalletModal = ({
);
};
-
-export type SetBurnerProps = { setBurner: (burner: Burner) => () => void };
-
-const DelegationButton = ({
- externalWalletClient,
- setBurner,
-}: SetBurnerProps & {
- externalWalletClient: WalletClient;
-}) => {
- const { chains, switchChain } = useSwitchChain();
- const { chainId } = useAccount();
- const { status, setupDelegation } = useDelegation(externalWalletClient);
-
- const wrongNetwork = useMemo(() => {
- if (!chainId) return true;
- const chainIds = chains.map(chain => chain.id);
- return !chainIds.includes(chainId);
- }, [chainId, chains]);
-
- if (wrongNetwork) {
- return (
-
- );
- }
-
- if (status === 'delegated') {
- return (
-
- );
- }
-
- return ;
-};
-
-const SetBurner = ({
- externalWalletAccountAddress,
- setBurner,
-}: SetBurnerProps & { externalWalletAccountAddress: Hex }) => {
- const { network } = useMUD();
-
- useEffect(
- () => setBurner(createBurner(network, externalWalletAccountAddress)),
- [externalWalletAccountAddress, network, setBurner],
- );
-
- return null;
-};
diff --git a/packages/client/src/components/DelegationButton.tsx b/packages/client/src/components/DelegationButton.tsx
new file mode 100644
index 000000000..93ec4a4e1
--- /dev/null
+++ b/packages/client/src/components/DelegationButton.tsx
@@ -0,0 +1,102 @@
+import { Button, useToast } from '@chakra-ui/react';
+import { useCallback, useEffect, useState } from 'react';
+import type { Account, Chain, Hex, Transport, WalletClient } from 'viem';
+import { useAccount, useSwitchChain } from 'wagmi';
+
+import { useMUD } from '../contexts/MUDContext';
+import { useDelegation } from '../hooks/useDelegation';
+import { type Burner, createBurner } from '../lib/mud/createBurner';
+import { getChainNameFromId, isSupportedChain } from '../lib/web3';
+
+export type SetBurnerProps = { setBurner: (burner: Burner) => () => void };
+
+export const DelegationButton = ({
+ externalWalletClient,
+ onClose,
+ setBurner,
+}: SetBurnerProps & {
+ externalWalletClient: WalletClient;
+ onClose?: () => void;
+}): JSX.Element => {
+ const { chains, switchChain } = useSwitchChain();
+ const { chainId } = useAccount();
+ const { status, setupDelegation } = useDelegation(externalWalletClient);
+ const toast = useToast();
+
+ const [isDelegating, setIsDelegating] = useState(false);
+
+ const onSetupDelegation = useCallback(async () => {
+ try {
+ if (!setupDelegation) {
+ throw new Error('Delegation setup function not available');
+ }
+
+ setIsDelegating(true);
+ await setupDelegation();
+
+ toast({
+ title: 'Delegation successful',
+ status: 'success',
+ duration: 5000,
+ isClosable: true,
+ });
+
+ if (onClose) {
+ onClose();
+ }
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error(error);
+
+ toast({
+ title: 'Delegation failed',
+ status: 'error',
+ duration: 5000,
+ isClosable: true,
+ });
+ } finally {
+ setIsDelegating(false);
+ }
+ }, [onClose, setupDelegation, toast]);
+
+ if (!isSupportedChain(chainId)) {
+ return (
+
+ );
+ }
+
+ if (status === 'delegated') {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+};
+
+const SetBurner = ({
+ externalWalletAccountAddress,
+ setBurner,
+}: SetBurnerProps & { externalWalletAccountAddress: Hex }) => {
+ const { network } = useMUD();
+
+ useEffect(
+ () => setBurner(createBurner(network, externalWalletAccountAddress)),
+ [externalWalletAccountAddress, network, setBurner],
+ );
+
+ return null;
+};
diff --git a/packages/client/src/contexts/Web3Provider.tsx b/packages/client/src/contexts/Web3Provider.tsx
index eac04977d..60e4a4757 100644
--- a/packages/client/src/contexts/Web3Provider.tsx
+++ b/packages/client/src/contexts/Web3Provider.tsx
@@ -9,10 +9,7 @@ import {
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createConfig, http, WagmiProvider } from 'wagmi';
-import {
- SUPPORTED_CHAINS,
- WALLET_CONNECT_PROJECT_ID,
-} from '../lib/web3/constants';
+import { SUPPORTED_CHAINS, WALLET_CONNECT_PROJECT_ID } from '../lib/web3';
const { wallets } = getDefaultWallets();
diff --git a/packages/client/src/lib/web3/constants.ts b/packages/client/src/lib/web3/constants.ts
index b0acfb2b1..c77b8b05c 100644
--- a/packages/client/src/lib/web3/constants.ts
+++ b/packages/client/src/lib/web3/constants.ts
@@ -3,6 +3,16 @@ import { anvil, baseSepolia, Chain } from 'wagmi/chains';
export const WALLET_CONNECT_PROJECT_ID = import.meta.env
.VITE_WALLET_CONNECT_PROJECT_ID;
+export const CHAIN_NAME_TO_ID: { [key: string]: number } = {
+ Anvil: anvil.id,
+ 'Base Sepolia': baseSepolia.id,
+};
+
+export const CHAIN_ID_TO_LABEL: { [key: number]: string } = {
+ [anvil.id]: 'Anvil',
+ [baseSepolia.id]: 'Base Sepolia',
+};
+
const getSupportedChains = () => {
if (import.meta.env.DEV) {
return [anvil] as const;
@@ -18,6 +28,23 @@ const validateConfig = () => {
if (!WALLET_CONNECT_PROJECT_ID) {
throw new Error('VITE_WALLET_CONNECT_PROJECT_ID is not set');
}
+
+ SUPPORTED_CHAINS.forEach(chain => {
+ if (!CHAIN_ID_TO_LABEL[chain.id]) {
+ throw new Error(`CHAIN_ID_TO_LABEL[${chain.id}] is not set`);
+ }
+
+ if (
+ !CHAIN_NAME_TO_ID[CHAIN_ID_TO_LABEL[chain.id]] ||
+ CHAIN_NAME_TO_ID[CHAIN_ID_TO_LABEL[chain.id]] !== chain.id
+ ) {
+ throw new Error(
+ `CHAIN_NAME_TO_ID[${
+ CHAIN_ID_TO_LABEL[chain.id]
+ }] is not set or does not match ${chain.id}`,
+ );
+ }
+ });
};
validateConfig();
diff --git a/packages/client/src/lib/web3/helpers.ts b/packages/client/src/lib/web3/helpers.ts
new file mode 100644
index 000000000..88beed007
--- /dev/null
+++ b/packages/client/src/lib/web3/helpers.ts
@@ -0,0 +1,27 @@
+import {
+ CHAIN_ID_TO_LABEL,
+ CHAIN_NAME_TO_ID,
+ SUPPORTED_CHAINS,
+} from './constants';
+
+export const isSupportedChain = (
+ chainId: number | string | bigint | undefined,
+): boolean =>
+ chainId !== undefined &&
+ SUPPORTED_CHAINS.find(c => c.id === Number(chainId)) !== undefined;
+
+export const getChainIdFromName = (chainLabel: string): number | undefined => {
+ const chainId = CHAIN_NAME_TO_ID[chainLabel];
+ if (!chainId || !isSupportedChain(chainId)) {
+ return undefined;
+ }
+ return chainId;
+};
+
+export const getChainNameFromId = (chainId: number): string | undefined => {
+ if (!chainId || !isSupportedChain(chainId)) {
+ return undefined;
+ }
+
+ return CHAIN_ID_TO_LABEL[chainId];
+};
diff --git a/packages/client/src/lib/web3/index.ts b/packages/client/src/lib/web3/index.ts
new file mode 100644
index 000000000..5a9087d1d
--- /dev/null
+++ b/packages/client/src/lib/web3/index.ts
@@ -0,0 +1,2 @@
+export * from './constants';
+export * from './helpers';
diff --git a/packages/client/src/utils/theme.ts b/packages/client/src/utils/theme.ts
index c4482680d..41afb941d 100644
--- a/packages/client/src/utils/theme.ts
+++ b/packages/client/src/utils/theme.ts
@@ -25,11 +25,17 @@ const Button = {
color: 'white',
px: 10,
py: 6,
+ _active: {
+ bg: 'rgba(0, 0, 0, 1)',
+ },
_hover: {
bg: 'rgba(0, 0, 0, 0.8)',
},
- _active: {
- bg: 'rgba(0, 0, 0, 0.7)',
+ _loading: {
+ bg: 'rgba(0, 0, 0, 0.8)',
+ _hover: {
+ bg: 'rgba(0, 0, 0, 0.8)',
+ },
},
},
},
From 94cd3647e003492f0e5702d47e2fd32b8b8b1026 Mon Sep 17 00:00:00 2001
From: ECWireless
Date: Thu, 30 May 2024 08:03:30 -0600
Subject: [PATCH 2/2] Add better error handling
---
.../src/components/DelegationButton.tsx | 24 +++--------
packages/client/src/hooks/useToast.ts | 43 +++++++++++++++++++
packages/client/src/utils/errors.ts | 18 ++++++++
3 files changed, 67 insertions(+), 18 deletions(-)
create mode 100644 packages/client/src/hooks/useToast.ts
create mode 100644 packages/client/src/utils/errors.ts
diff --git a/packages/client/src/components/DelegationButton.tsx b/packages/client/src/components/DelegationButton.tsx
index 93ec4a4e1..e16a263dd 100644
--- a/packages/client/src/components/DelegationButton.tsx
+++ b/packages/client/src/components/DelegationButton.tsx
@@ -1,10 +1,11 @@
-import { Button, useToast } from '@chakra-ui/react';
+import { Button } from '@chakra-ui/react';
import { useCallback, useEffect, useState } from 'react';
import type { Account, Chain, Hex, Transport, WalletClient } from 'viem';
import { useAccount, useSwitchChain } from 'wagmi';
import { useMUD } from '../contexts/MUDContext';
import { useDelegation } from '../hooks/useDelegation';
+import { useToast } from '../hooks/useToast';
import { type Burner, createBurner } from '../lib/mud/createBurner';
import { getChainNameFromId, isSupportedChain } from '../lib/web3';
@@ -21,7 +22,7 @@ export const DelegationButton = ({
const { chains, switchChain } = useSwitchChain();
const { chainId } = useAccount();
const { status, setupDelegation } = useDelegation(externalWalletClient);
- const toast = useToast();
+ const { renderError, renderSuccess } = useToast();
const [isDelegating, setIsDelegating] = useState(false);
@@ -34,30 +35,17 @@ export const DelegationButton = ({
setIsDelegating(true);
await setupDelegation();
- toast({
- title: 'Delegation successful',
- status: 'success',
- duration: 5000,
- isClosable: true,
- });
+ renderSuccess('Delegation successful');
if (onClose) {
onClose();
}
} catch (error) {
- // eslint-disable-next-line no-console
- console.error(error);
-
- toast({
- title: 'Delegation failed',
- status: 'error',
- duration: 5000,
- isClosable: true,
- });
+ renderError(error, 'Failed to delegate');
} finally {
setIsDelegating(false);
}
- }, [onClose, setupDelegation, toast]);
+ }, [onClose, renderError, renderSuccess, setupDelegation]);
if (!isSupportedChain(chainId)) {
return (
diff --git a/packages/client/src/hooks/useToast.ts b/packages/client/src/hooks/useToast.ts
new file mode 100644
index 000000000..bcab045e6
--- /dev/null
+++ b/packages/client/src/hooks/useToast.ts
@@ -0,0 +1,43 @@
+import { useToast as useChakraToast } from '@chakra-ui/react';
+
+import { getErrorMessage, USER_ERRORS } from '../utils/errors';
+
+export const useToast = (): {
+ renderError: (error: unknown, defaultError?: string) => void;
+ renderWarning: (msg: string) => void;
+ renderSuccess: (msg: string) => void;
+} => {
+ const toast = useChakraToast();
+
+ const renderError = (error: unknown, defaultError?: string) => {
+ const errorMsg = getErrorMessage(error);
+
+ if (USER_ERRORS.includes(errorMsg)) {
+ return;
+ }
+
+ toast({
+ description: getErrorMessage(error, defaultError),
+ position: 'top',
+ status: 'error',
+ });
+ };
+
+ const renderWarning = (msg: string) => {
+ toast({
+ description: msg,
+ position: 'top',
+ status: 'warning',
+ });
+ };
+
+ const renderSuccess = (msg: string) => {
+ toast({
+ description: msg,
+ position: 'top',
+ status: 'success',
+ });
+ };
+
+ return { renderError, renderWarning, renderSuccess };
+};
diff --git a/packages/client/src/utils/errors.ts b/packages/client/src/utils/errors.ts
new file mode 100644
index 000000000..e3f70af26
--- /dev/null
+++ b/packages/client/src/utils/errors.ts
@@ -0,0 +1,18 @@
+export const USER_ERRORS = ['User denied signature'];
+
+export const getErrorMessage = (
+ error: unknown,
+ defaultError: string = 'Unknown error',
+): string => {
+ // eslint-disable-next-line no-console
+ console.error(error);
+ if (typeof error === 'string') {
+ return error;
+ }
+
+ if ((error as Error)?.message?.toLowerCase().includes('user denied')) {
+ return USER_ERRORS[0];
+ }
+
+ return (error as Error)?.message || defaultError;
+};