Skip to content

Commit

Permalink
fix(website): CAN-502 - safes from custom chains (#1463)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicosampler authored Oct 15, 2024
1 parent 513d342 commit e943956
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 112 deletions.
76 changes: 0 additions & 76 deletions packages/website/src/constants/deployChains.ts

This file was deleted.

61 changes: 50 additions & 11 deletions packages/website/src/features/Deploy/SafeAddressInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { includes } from '@/helpers/array';
import { State, useStore } from '@/helpers/store';
import {
isValidSafe,
isValidSafeFromSafeString,
isValidSafeString,
parseSafe,
SafeString,
Expand All @@ -12,7 +13,14 @@ import {
useWalletPublicSafes,
} from '@/hooks/safe';
import { CloseIcon, WarningIcon } from '@chakra-ui/icons';
import { FormControl, IconButton, Text, Flex, Tooltip } from '@chakra-ui/react';
import {
FormControl,
IconButton,
Text,
Flex,
Tooltip,
FormErrorMessage,
} from '@chakra-ui/react';
import {
chakraComponents,
ChakraStylesConfig,
Expand All @@ -21,10 +29,11 @@ import {
OptionProps,
SingleValue,
SingleValueProps,
MenuListProps,
} from 'chakra-react-select';
import deepEqual from 'fast-deep-equal';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { useSwitchChain } from 'wagmi';
import omit from 'lodash/omit';

Expand All @@ -44,6 +53,7 @@ export function SafeAddressInput() {
const currentSafe = useStore((s) => s.currentSafe);
const safeAddresses = useStore((s) => s.safeAddresses);
const setCurrentSafe = useStore((s) => s.setCurrentSafe);
const [inputErrorText, setInputErrorText] = useState('');

// This state prevents the initialization useEffect (which sets the selected safe from the url or the currentSafe)
// from running when clearing the input
Expand Down Expand Up @@ -84,10 +94,10 @@ export function SafeAddressInput() {
return;
}

const parsedSafeInput = parseSafe(safeString);
if (!parsedSafeInput) {
if (!isValidSafeString(safeString)) {
return;
}
const parsedSafeInput = parseSafe(safeString);

if (!isValidSafe(parsedSafeInput, chains)) {
return;
Expand All @@ -106,6 +116,9 @@ export function SafeAddressInput() {
);

function handleSafeDelete(safeString: SafeString) {
if (!isValidSafeString(safeString)) {
return;
}
deleteSafe(parseSafe(safeString));
}

Expand Down Expand Up @@ -166,7 +179,7 @@ export function SafeAddressInput() {

if (address && chainId) {
const safeFromUrl = parseSafe(`${chainId}:${address}`);
if (!safeFromUrl || !isValidSafe(safeFromUrl, chains)) {
if (!isValidSafe(safeFromUrl, chains)) {
throw new Error(
"We couldn't find a safe for the specified chain. If it is a custom chain, please ensure that a custom provider is properly configured in the settings page."
);
Expand Down Expand Up @@ -229,17 +242,31 @@ export function SafeAddressInput() {
onCreateOption={handleNewOrSelectedSafe}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore-next-line
//@ts-ignore-next-line
onDeleteOption={(selected: SafeOption) =>
handleSafeDelete(selected?.value || null)
handleSafeDelete(selected.value)
}
isValidNewOption={isValidSafeString}
isValidNewOption={(safeString) => {
setInputErrorText('');
const res = isValidSafeFromSafeString(safeString, chains);
if (!res && safeString !== '') {
setInputErrorText(
'Invalid Safe Address. If you are using a custom chain, configure a custom PRC in the settings page.'
);
}
return res;
}}
components={{
Option: DeletableOption,
SingleValue: SelectedOption,
MenuList: CustomMenuList,
MenuList: (props) => (
<CustomMenuList {...props} inputErrorText={inputErrorText} />
),
}}
/>
<FormErrorMessage>{inputErrorText}</FormErrorMessage>
</FormControl>

{currentSafe && pendingServiceTransactions.count > 0 && (
<Tooltip
label={`There ${
Expand Down Expand Up @@ -319,11 +346,23 @@ function DeletableOption({
);
}

function CustomMenuList({ children, ...props }: any) {
function CustomMenuList({
children,
inputErrorText,
...props
}: MenuListProps<SafeOption> & {
inputErrorText: string;
}) {
return (
<chakraComponents.MenuList {...props}>
<Text color="gray.400" fontSize="xs" my={2} ml={4}>
To add a Safe, enter it in the format chainId:safeAddress
<Text
color={inputErrorText ? 'red.400' : 'gray.400'}
fontSize="xs"
my={2}
ml={4}
>
{inputErrorText ||
'To add a Safe, enter it in the format chainId:safeAddress'}
</Text>
{children}
</chakraComponents.MenuList>
Expand Down
3 changes: 0 additions & 3 deletions packages/website/src/helpers/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import uniqWith from 'lodash/uniqWith';
import { Address, TransactionRequestBase, AbiFunction } from 'viem';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { chains } from '@/constants/deployChains';
import { BuildState } from '@/hooks/cannon';
import { includes } from '@/helpers/array';
import { externalLinks } from '@/constants/externalLinks';
Expand All @@ -20,8 +19,6 @@ export type IdentifiableTxn = {
pkgUrl: string;
};

export type ChainId = (typeof chains)[number]['id'];

export type SafeDefinition = {
chainId: number;
address: Address;
Expand Down
40 changes: 20 additions & 20 deletions packages/website/src/hooks/safe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,36 @@ import {
import { useAccount, useReadContracts } from 'wagmi';
import SafeABI from '@/abi/Safe.json';
import SafeABI_v1_4_1 from '@/abi/Safe-v1.4.1.json';
import { chains } from '@/constants/deployChains';
import * as onchainStore from '@/helpers/onchain-store';
import { ChainId, SafeDefinition, useStore } from '@/helpers/store';
import { SafeDefinition, useStore } from '@/helpers/store';
import { SafeTransaction } from '@/types/SafeTransaction';
import { useCannonChains } from '@/providers/CannonProvidersProvider';
import { chainMetadata, useCannonChains } from '@/providers/CannonProvidersProvider';

export type SafeString = `${ChainId}:${Address}`;
export type SafeString = `${number}:${Address}`;

export function safeToString(safe: SafeDefinition): SafeString {
return `${safe.chainId as ChainId}:${safe.address}`;
return `${safe.chainId}:${safe.address}`;
}

const addressStringRegex = /^[1-9][0-9]*:0x[a-fA-F0-9]{40}$/;

export function isValidSafeString(safeString: string): boolean {
if (typeof safeString !== 'string') return false;
if (!addressStringRegex.test(safeString)) return false;
const chainId = Number.parseInt(safeString.split(':')[0]);
return chains.some((chain) => chain.id === chainId);
if (isNaN(chainId)) return false;
const address = safeString.split(':')[1];
return isAddress(address || '');
}

export function isValidSafeFromSafeString(safeString: string, chains: Chain[]): boolean {
if (!isValidSafeString(safeString)) return false;
const chainId = Number.parseInt(safeString.split(':')[0]);
const existChain = chains.some((chain) => chain.id === chainId);
return existChain;
}

export function parseSafe(safeString: string): SafeDefinition | null {
if (!isValidSafeString(safeString)) return null;
export function parseSafe(safeString: string): SafeDefinition {
const [chainId, address] = safeString.split(':');
return {
chainId: Number.parseInt(chainId) as ChainId,
chainId: Number.parseInt(chainId),
address: getAddress(address),
};
}
Expand All @@ -55,12 +59,9 @@ export function isValidSafe(safe: SafeDefinition, supportedChains: Chain[]): boo
);
}

function _getShortName(safe: SafeDefinition) {
return chains.find((chain) => chain.id === safe.chainId)?.shortName;
}

function _getSafeShortNameAddress(safe: SafeDefinition) {
return `${_getShortName(safe)}:${getAddress(safe.address)}`;
const metadata = chainMetadata[safe.chainId];
return `${metadata.shortName || safe.chainId}:${getAddress(safe.address)}`;
}

export function getSafeUrl(safe: SafeDefinition, pathname = '/home') {
Expand All @@ -71,12 +72,11 @@ export function getSafeUrl(safe: SafeDefinition, pathname = '/home') {
function _createSafeApiKit(chainId: number) {
if (!chainId) return null;

const chain = chains.find((chain) => chain.id === chainId);

const chain = chainMetadata[chainId];
if (!chain?.serviceUrl) return null;

return new SafeApiKit({
chainId: BigInt(chain.id),
chainId: BigInt(chainId),
txServiceUrl: new URL('/api', chain.serviceUrl).toString(),
});
}
Expand Down
Loading

0 comments on commit e943956

Please sign in to comment.