Commit db6c1650 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Highlight Safe wallets (#1174)

* add tag and icon for safe contracts

* surpress sentry error log and describe feature in the docs

* refine logs at container start
parent f410f651
......@@ -11,6 +11,7 @@ export { default as marketplace } from './marketplace';
export { default as mixpanel } from './mixpanel';
export { default as restApiDocs } from './restApiDocs';
export { default as rollup } from './rollup';
export { default as safe } from './safe';
export { default as sentry } from './sentry';
export { default as sol2uml } from './sol2uml';
export { default as stats } from './stats';
......
import type { Feature } from './types';
import chain from '../chain';
// https://docs.safe.global/safe-core-api/available-services
const SAFE_API_MAP: Record<string, string> = {
'42161': 'https://safe-transaction-arbitrum.safe.global',
'1313161554': 'https://safe-transaction-aurora.safe.global',
'43114': 'https://safe-transaction-avalanche.safe.global',
'8453': 'https://safe-transaction-base.safe.global',
'84531': 'https://safe-transaction-base-testnet.safe.global',
'56': 'https://safe-transaction-bsc.safe.global',
'42220': 'https://safe-transaction-celo.safe.global',
'1': 'https://safe-transaction-mainnet.safe.global',
'100': 'https://safe-transaction-gnosis-chain.safe.global',
'5': 'https://safe-transaction-goerli.safe.global',
'10': 'https://safe-transaction-optimism.safe.global',
'137': 'https://safe-transaction-polygon.safe.global',
};
function getApiUrl(): string | undefined {
if (!chain.id) {
return;
}
const apiHost = SAFE_API_MAP[chain.id];
if (!apiHost) {
return;
}
return `${ apiHost }/api/v1/safes/`;
}
const title = 'Safe address tags';
const config: Feature<{ apiUrl: string }> = (() => {
const apiUrl = getApiUrl();
if (apiUrl) {
return Object.freeze({
title,
isEnabled: true,
apiUrl,
});
}
return Object.freeze({
title,
isEnabled: false,
});
})();
export default config;
......@@ -13,6 +13,7 @@ if [ $? -ne 0 ]; then
else
echo "👍 Favicons bundle successfully generated."
fi
echo
# Execute script for replace build-time ENVs placeholders with their values at runtime
./replace_envs.sh
......
......@@ -31,4 +31,6 @@ function replace_envs {
done < $envFilename
}
replace_envs
\ No newline at end of file
echo ⏳ Replacing build-stage ENV placholders with their run-time values...
replace_envs
echo 👍 Done!
\ No newline at end of file
......@@ -382,6 +382,12 @@ This feature is **enabled by default** with the `['metamask']` value. To switch
&nbsp;
### Safe{Core} address tags
For the smart contract addresses which are [Safe{Core} accounts](https://safe.global/) public tag "Multisig: Safe" will be displayed in the address page header along side to Safe logo. The Safe service is available only for certain networks, see full list [here](https://docs.safe.global/safe-core-api/available-services). Based on provided value of `NEXT_PUBLIC_NETWORK_ID`, the feature will be enabled or disabled.
&nbsp;
### Sentry error monitoring
| Variable | Type| Description | Compulsoriness | Default value | Example value |
......
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="background:#12ff80" viewBox="0 0 661.6 661.5">
<path d="M532 330.7h-49.4c-14.8 0-26.7 12-26.7 26.7v71.7c0 14.8-12 26.7-26.7 26.7H232.5c-14.8 0-26.7 12-26.7 26.7V532c0 14.8 12 26.7 26.7 26.7h208c14.8 0 26.5-12 26.5-26.7v-39.6c0-14.8 12-25.2 26.7-25.2H532c14.8 0 26.7-12 26.7-26.7v-83.3c0-14.9-12-26.5-26.7-26.5zm-326.2-98.2c0-14.8 12-26.7 26.7-26.7H429c14.8 0 26.7-12 26.7-26.7v-49.4c0-14.8-12-26.7-26.7-26.7H221.1c-14.8 0-26.7 12-26.7 26.7v38.1c0 14.8-12 26.7-26.7 26.7h-38c-14.8 0-26.7 12-26.7 26.7v83.4c0 14.8 12 26.1 26.8 26.1h49.4c14.8 0 26.7-12 26.7-26.7l-.1-71.5zm101.7 46.3H355c15.5 0 28 12.6 28 28v47.5c0 15.5-12.6 28-28 28h-47.5c-15.5 0-28-12.6-28-28v-47.5c0-15.5 12.6-28 28-28z"/>
</svg>
......@@ -14,6 +14,7 @@ export interface Params {
interface Meta {
resource?: ResourcePath;
omitSentryErrorLog?: boolean;
}
export default function useFetch() {
......@@ -49,7 +50,10 @@ export default function useFetch() {
status: response.status,
statusText: response.statusText,
};
Sentry.captureException(new Error('Client fetch failed'), { extra: { ...error, ...meta }, tags: { source: 'fetch' } });
if (!meta?.omitSentryErrorLog) {
Sentry.captureException(new Error('Client fetch failed'), { extra: { ...error, ...meta }, tags: { source: 'fetch' } });
}
return response.json().then(
(jsonError) => Promise.reject({
......
import { useQuery } from '@tanstack/react-query';
import config from 'configs/app';
import useFetch from 'lib/hooks/useFetch';
const feature = config.features.safe;
export default function useIsSafeAddress(hash: string | undefined): boolean {
const fetch = useFetch();
const { data } = useQuery(
[ 'safe_transaction_api', hash ],
async() => {
if (!feature.isEnabled || !hash) {
return Promise.reject();
}
return fetch(`${ feature.apiUrl }/${ hash }`, undefined, { omitSentryErrorLog: true });
},
{
enabled: feature.isEnabled && Boolean(hash),
refetchOnMount: false,
},
);
return Boolean(data);
}
......@@ -11,6 +11,7 @@ function generateCspPolicy() {
descriptors.googleReCaptcha(),
descriptors.mixpanel(),
descriptors.monaco(),
descriptors.safe(),
descriptors.sentry(),
descriptors.walletConnect(),
);
......
......@@ -6,5 +6,6 @@ export { googleFonts } from './googleFonts';
export { googleReCaptcha } from './googleReCaptcha';
export { mixpanel } from './mixpanel';
export { monaco } from './monaco';
export { safe } from './safe';
export { sentry } from './sentry';
export { walletConnect } from './walletConnect';
import type CspDev from 'csp-dev';
import config from 'configs/app';
export function safe(): CspDev.DirectiveDescriptor {
if (!config.features.safe.isEnabled) {
return {};
}
return {
'connect-src': [
'*.safe.global',
],
};
}
......@@ -11,6 +11,7 @@ import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/contexts/app';
import useContractTabs from 'lib/hooks/useContractTabs';
import useIsMobile from 'lib/hooks/useIsMobile';
import useIsSafeAddress from 'lib/hooks/useIsSafeAddress';
import getQueryParamString from 'lib/router/getQueryParamString';
import { ADDRESS_INFO, ADDRESS_TABS_COUNTERS } from 'stubs/address';
import AddressBlocksValidated from 'ui/address/AddressBlocksValidated';
......@@ -62,6 +63,8 @@ const AddressPageContent = () => {
},
});
const isSafeAddress = useIsSafeAddress(!addressQuery.isPlaceholderData && addressQuery.data?.is_contract ? hash : undefined);
const contractTabs = useContractTabs(addressQuery.data);
const tabs: Array<RoutedTab> = React.useMemo(() => {
......@@ -147,6 +150,7 @@ const AddressPageContent = () => {
addressQuery.data?.is_contract ? { label: 'contract', display_name: 'Contract' } : { label: 'eoa', display_name: 'EOA' },
addressQuery.data?.implementation_address ? { label: 'proxy', display_name: 'Proxy' } : undefined,
addressQuery.data?.token ? { label: 'token', display_name: 'Token' } : undefined,
isSafeAddress ? { label: 'safe', display_name: 'Multisig: Safe' } : undefined,
] }
contentAfter={
<NetworkExplorers type="address" pathParam={ hash } ml="auto" hideText={ isMobile }/>
......
......@@ -5,6 +5,7 @@ import type { Address } from 'types/api/address';
import type { TokenInfo } from 'types/api/token';
import config from 'configs/app';
import useIsSafeAddress from 'lib/hooks/useIsSafeAddress';
import AddressFavoriteButton from 'ui/address/details/AddressFavoriteButton';
import AddressQrCode from 'ui/address/details/AddressQrCode';
import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet';
......@@ -19,6 +20,8 @@ interface Props {
}
const AddressHeadingInfo = ({ address, token, isLinkDisabled, isLoading }: Props) => {
const isSafeAddress = useIsSafeAddress(!isLoading && address.is_contract ? address.hash : undefined);
return (
<Flex alignItems="center">
<AddressEntity
......@@ -28,6 +31,7 @@ const AddressHeadingInfo = ({ address, token, isLinkDisabled, isLoading }: Props
fontSize="lg"
fontWeight={ 500 }
noLink={ isLinkDisabled }
isSafeAddress={ isSafeAddress }
/>
{ !isLoading && address.is_contract && token && <AddressAddToWallet ml={ 2 } token={ token }/> }
{ !isLoading && !address.is_contract && config.features.account.isEnabled && (
......
......@@ -8,6 +8,7 @@ import type { AddressParam } from 'types/api/addressParams';
import { route } from 'nextjs-routes';
import iconSafe from 'icons/brands/safe.svg';
import iconContractVerified from 'icons/contract_verified.svg';
import iconContract from 'icons/contract.svg';
import * as EntityBase from 'ui/shared/entities/base/components';
......@@ -29,7 +30,7 @@ const Link = chakra((props: LinkProps) => {
);
});
type IconProps = Pick<EntityProps, 'address' | 'isLoading' | 'iconSize' | 'noIcon'> & {
type IconProps = Pick<EntityProps, 'address' | 'isLoading' | 'iconSize' | 'noIcon' | 'isSafeAddress'> & {
asProp?: As;
};
......@@ -48,6 +49,15 @@ const Icon = (props: IconProps) => {
}
if (props.address.is_contract) {
if (props.isSafeAddress) {
return (
<EntityBase.Icon
{ ...props }
asProp={ iconSafe }
/>
);
}
if (props.address.is_verified) {
return (
<Tooltip label="Verified contract">
......@@ -121,6 +131,7 @@ const Container = EntityBase.Container;
export interface EntityProps extends EntityBase.EntityBaseProps {
address: Pick<AddressParam, 'hash' | 'name' | 'is_contract' | 'is_verified' | 'implementation_name'>;
isSafeAddress?: boolean;
}
const AddressEntry = (props: EntityProps) => {
......
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