Skip to content

Commit

Permalink
Merge pull request #2750 from decentdao/release/v0.8.1
Browse files Browse the repository at this point in the history
Release/v0.8.1
  • Loading branch information
adamgall authored Feb 27, 2025
2 parents 610a554 + f7a7685 commit 26a2efe
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 119 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "decent-interface",
"version": "0.8.0",
"version": "0.8.1",
"private": true,
"dependencies": {
"@amplitude/analytics-browser": "^2.11.1",
Expand Down
49 changes: 8 additions & 41 deletions src/components/ui/page/Layout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Box, Container, Grid, GridItem, Show, Text } from '@chakra-ui/react';
import { Box, Container, Grid, GridItem, Show } from '@chakra-ui/react';
import { useRef } from 'react';
import { Outlet, useMatches } from 'react-router-dom';
import { Outlet } from 'react-router-dom';
import {
MAX_CONTENT_WIDTH,
SIDEBAR_WIDTH,
Expand All @@ -24,50 +24,22 @@ export function Layout() {

useNavigationScrollReset();

const isReindexing = true;
const matches = useMatches();
const onDao = matches.some(
_match => _match.pathname !== '/' && !_match.pathname.startsWith('/create'),
);

const showReindexingBanner = isReindexing && onDao;

return (
<Grid
templateAreas={{
base: `"banner banner"
"header header"
base: `"header header"
"main main"`,
md: `"banner banner"
"header header"
md: `"header header"
"nav main"
"footer footer"`,
}}
gridTemplateColumns={`${SIDEBAR_WIDTH} 1fr`}
gridTemplateRows={{
base: `auto ${HEADER_HEIGHT} 100%`,
md: `auto ${HEADER_HEIGHT} minmax(${CONTENT_HEIGHT}, 100%) ${FOOTER_HEIGHT}`,
base: `${HEADER_HEIGHT} 100%`,
md: `${HEADER_HEIGHT} minmax(${CONTENT_HEIGHT}, 100%) ${FOOTER_HEIGHT}`,
}}
position="relative"
>
<GridItem area="banner">
{showReindexingBanner ? (
<Box
bg="lilac--2"
textAlign="center"
p={3}
position="fixed"
w="full"
zIndex="2"
fontWeight="bold"
transformOrigin="top"
>
<Text>
Decent&apos;s indexers are rebuilding. We are working to restore services soon.
</Text>
</Box>
) : null}
</GridItem>
<GridItem
area="header"
ref={headerContainerRef}
Expand All @@ -79,7 +51,6 @@ export function Layout() {
w="full"
maxW="100vw"
zIndex="1"
mt={showReindexingBanner ? '48px' : '0px'}
>
<Header headerContainerRef={headerContainerRef} />
</Box>
Expand All @@ -92,19 +63,15 @@ export function Layout() {
flexDirection="column"
position="fixed"
ml={6}
top={showReindexingBanner ? `calc(${HEADER_HEIGHT} + 48px)` : HEADER_HEIGHT}
minHeight={{
base: undefined,
md: `calc(100vh - ${HEADER_HEIGHT} - ${showReindexingBanner ? '48px' : '0px'})`,
}}
top={HEADER_HEIGHT}
minHeight={{ base: undefined, md: `calc(100vh - ${HEADER_HEIGHT})` }}
>
<NavigationLinks />
</GridItem>
</Show>
<GridItem
area="main"
mx={{ base: '0.5rem', md: '1.5rem' }}
mt={showReindexingBanner ? '48px' : '0px'}
>
<Container
maxWidth={MAX_CONTENT_WIDTH}
Expand Down
35 changes: 20 additions & 15 deletions src/hooks/DAO/useSearchDao.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Address } from 'viem';
import { getSafeAPI } from '../../providers/App/hooks/useSafeAPI';
import {
supportedEnsNetworks,
supportedNetworks,
useNetworkConfigStore,
} from '../../providers/NetworkConfig/useNetworkConfigStore';
import { getIsSafe } from '../safe/useIsSafe';
import { useResolveENSName } from '../utils/useResolveENSName';

type ResolvedAddressWithChainId = {
Expand All @@ -29,22 +29,27 @@ export const useSearchDao = () => {
const findSafes = useCallback(
async (resolvedAddressesWithChainId: { address: Address; chainId: number }[]) => {
/*
This function only checks if the address is on any of the EVM networks.
The same safe could of on multiple networks
Changes requested inside getSafeCreationInfo
This function only checks if the address is a Safe on any of the EVM networks.
The same Safe could of on multiple networks
*/
for await (const resolved of resolvedAddressesWithChainId) {
try {
const safeAPI = getSafeAPI(getConfigByChainId(resolved.chainId));
await safeAPI.getSafeCreationInfo(resolved.address);

setSafeResolvedAddressesWithPrefix(prevState => [...prevState, resolved]);
} catch (e) {
// Safe not found
continue;
}
}
const realSafes = (
await Promise.all(
resolvedAddressesWithChainId.map(async resolved => {
const networkConfig = getConfigByChainId(resolved.chainId);
const isSafe = await getIsSafe(resolved.address, networkConfig);
if (isSafe) {
return resolved;
} else {
return null;
}
}),
)
).filter(safe => safe !== null);

// We're left with a list of chains and addresses
// (all the same address) that have a Safe at that address.
setSafeResolvedAddressesWithPrefix(realSafes);
},
[getConfigByChainId],
);
Expand Down
109 changes: 90 additions & 19 deletions src/hooks/safe/useIsSafe.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,110 @@
import {
getSafeL2SingletonDeployment,
getSafeSingletonDeployment,
} from '@safe-global/safe-deployments';
import { useEffect, useState } from 'react';
import { isAddress } from 'viem';
import { useSafeAPI } from '../../providers/App/hooks/useSafeAPI';

/**
* A hook which determines whether the provided Ethereum address is a Safe
* smart contract address on the currently connected chain (chainId).
*
* The state can be either true/false or undefined, if a network call is currently
* being performed to determine that status.
*
* @param address the address to check
* @returns isSafe: whether the address is a Safe,
* isSafeLoading: true/false whether the isSafe status is still being determined
*/
import { Address, createPublicClient, http, isAddress, toHex } from 'viem';
import { getSafeContractDeploymentAddress } from '../../providers/NetworkConfig/networks/utils';
import { useNetworkConfigStore } from '../../providers/NetworkConfig/useNetworkConfigStore';
import { NetworkConfig } from '../../types/network';

const safeVersions = ['1.3.0', '1.4.1'];

const safe130bytecode =
'0x608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea2646970667358221220d1429297349653a4918076d650332de1a1068c5f3e07c5c82360c277770b955264736f6c63430007060033';
const safe141bytecode =
'0x608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea264697066735822122003d1488ee65e08fa41e58e888a9865554c535f2c77126a82cb4c0f917f31441364736f6c63430007060033';

function getSafeSingleton(chainId: number, safeVersion: string): string | undefined {
try {
const singleton = getSafeContractDeploymentAddress(
getSafeSingletonDeployment,
safeVersion,
chainId.toString(),
);
return singleton.toLowerCase().replace('0x', '');
} catch (err) {
return undefined;
}
}

function getSafeL2Singleton(chainId: number, safeVersion: string): string | undefined {
try {
const singleton = getSafeContractDeploymentAddress(
getSafeL2SingletonDeployment,
safeVersion,
chainId.toString(),
);
return singleton.toLowerCase().replace('0x', '');
} catch (err) {
return undefined;
}
}

function getSafeSingletons(chainId: number): string[] {
const safeSingletons = safeVersions
.map(version => getSafeSingleton(chainId, version))
.filter(singleton => singleton != undefined);
const safeL2Singletons = safeVersions
.map(version => getSafeL2Singleton(chainId, version))
.filter(singleton => singleton != undefined);
return [safeSingletons, safeL2Singletons].flat();
}

export async function getIsSafe(address: Address, networkConfig: NetworkConfig): Promise<boolean> {
try {
const publicClient = createPublicClient({
chain: networkConfig.chain,
transport: http(networkConfig.rpcEndpoint),
});

const bytecode = (await publicClient.getBytecode({ address: address }))?.toLowerCase();
if (bytecode != safe130bytecode && bytecode != safe141bytecode) {
return false;
}

const store = await publicClient.getStorageAt({
address: address,
slot: toHex(0),
});

if (store === undefined) {
return false;
}

// We have the bytecode, let's just find if any of the single addresses are there
const safeSingletons = getSafeSingletons(networkConfig.chain.id);
return safeSingletons.some(singleton => store === `0x000000000000000000000000${singleton}`);
} catch (error) {
console.log(error);
return false;
}
}

export const useIsSafe = (address: string | undefined) => {
const [isSafeLoading, setSafeLoading] = useState<boolean>(false);
const [isSafe, setIsSafe] = useState<boolean | undefined>();
const safeAPI = useSafeAPI();

// currently connected chain
const { chain, getConfigByChainId } = useNetworkConfigStore();
const networkConfig = getConfigByChainId(chain.id);

useEffect(() => {
setSafeLoading(true);
setIsSafe(undefined);

if (!address || !isAddress(address) || !safeAPI) {
if (!address || !isAddress(address)) {
setIsSafe(false);
setSafeLoading(false);
return;
}

safeAPI
.getSafeCreationInfo(address)
getIsSafe(address, networkConfig)
.then(setIsSafe)
.then(() => setIsSafe(true))
.catch(() => setIsSafe(false))
.finally(() => setSafeLoading(false));
}, [address, safeAPI]);
}, [address, networkConfig]);

return { isSafe, isSafeLoading };
};
41 changes: 0 additions & 41 deletions src/providers/App/hooks/useSafeAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import SafeApiKit, {
AllTransactionsListResponse,
AllTransactionsOptions,
ProposeTransactionProps,
SafeCreationInfoResponse,
SafeInfoResponse,
SafeMultisigTransactionListResponse,
SignatureResponse,
Expand Down Expand Up @@ -38,7 +37,6 @@ class EnhancedSafeApiKit extends SafeApiKit {
//
// - overridden functions
// - getSafeInfo ✅
// - getSafeCreationInfo 🟨 ENG-291
// - getAllTransactions 🟨 ENG-292
// - getNextNonce 🟨 ENG-293
// - getToken ✅
Expand Down Expand Up @@ -134,45 +132,6 @@ class EnhancedSafeApiKit extends SafeApiKit {
throw new Error('Failed to getSafeInfo()');
}

override async getSafeCreationInfo(safeAddress: Address): Promise<SafeCreationInfoResponse> {
try {
return await super.getSafeCreationInfo(safeAddress);
} catch (error) {
console.error('Error fetching getSafeCreationInfo from safeAPI:', error);
}

try {
type SafeClientCreationInfoResponse = {
readonly created: string;
readonly creator: string;
readonly transactionHash: string;
readonly factoryAddress: string;

readonly masterCopy: string;
readonly setupData: string;
};

const response: SafeClientCreationInfoResponse = await this._safeClientGet(
safeAddress,
'/transactions/creation',
);

return { ...response, singleton: response.masterCopy };
} catch (error) {
console.error('Error fetching getSafeCreationInfo from safe-client:', error);
}

try {
// TODO ENG-291
// add another layer of onchain fallback here
// use subgraph to get this data
} catch (error) {
console.error('Error fetching getSafeCreationInfo from subgraph:', error);
}

throw new Error('Failed to getSafeCreationInfo()');
}

private async _safeClientGet(safeAddress: Address, path: string): Promise<any> {
const value = await axios.get(`${this.safeClientUrlPrefix}${safeAddress}${path}`, {
headers: {
Expand Down

0 comments on commit 26a2efe

Please sign in to comment.