Skip to content

Commit

Permalink
feat: blockaid integration (#4029)
Browse files Browse the repository at this point in the history
* feat: blockaid integration

- exchanges redefine for blockaid
- redesigns warning component

* feat: better error handling

* fix: adjust design for errors

* feat: add powered by footer

* feat: add more chains, code cleanup

* fix: change URL and auth header

* fix: update .env variable in build.yml

* fix: throw error if validation is missing

* fix: loading state of blockaid block

* tests: adjust mock
  • Loading branch information
schmanu authored Sep 11, 2024
1 parent 41e9784 commit ab3871b
Show file tree
Hide file tree
Showing 25 changed files with 1,541 additions and 1,001 deletions.
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ NEXT_PUBLIC_FIREBASE_VAPID_KEY_PRODUCTION=
NEXT_PUBLIC_FIREBASE_OPTIONS_STAGING=
NEXT_PUBLIC_FIREBASE_VAPID_KEY_STAGING=

# Redefine
NEXT_PUBLIC_REDEFINE_API=
# Blockaid
NEXT_PUBLIC_BLOCKAID_CLIENT_ID

# Social Login
NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_STAGING=
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ runs:
NEXT_PUBLIC_SAFE_RELAY_SERVICE_URL_PRODUCTION: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_SAFE_GELATO_RELAY_SERVICE_URL_PRODUCTION }}
NEXT_PUBLIC_SAFE_RELAY_SERVICE_URL_STAGING: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_SAFE_GELATO_RELAY_SERVICE_URL_STAGING }}
NEXT_PUBLIC_IS_OFFICIAL_HOST: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_IS_OFFICIAL_HOST }}
NEXT_PUBLIC_REDEFINE_API: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_REDEFINE_API }}
NEXT_PUBLIC_BLOCKAID_CLIENT_ID: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_BLOCKAID_CLIENT_ID }}
NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_STAGING: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_STAGING }}
NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_PRODUCTION: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_SOCIAL_WALLET_OPTIONS_PRODUCTION }}
NEXT_PUBLIC_FIREBASE_OPTIONS_PRODUCTION: ${{ fromJSON(inputs.secrets).NEXT_PUBLIC_FIREBASE_OPTIONS_PRODUCTION }}
Expand Down
17 changes: 17 additions & 0 deletions public/images/transactions/blockaid-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import useSafeInfo from '@/hooks/useSafeInfo'
import { getRecoveryProposalTransactions } from '@/features/recovery/services/transaction'
import DecodedTx from '@/components/tx/DecodedTx'
import ErrorMessage from '@/components/tx/ErrorMessage'
import { RedefineBalanceChanges } from '@/components/tx/security/redefine/RedefineBalanceChange'
import ConfirmationTitle, { ConfirmationTitleTypes } from '@/components/tx/SignOrExecuteForm/ConfirmationTitle'
import TxChecks from '@/components/tx/SignOrExecuteForm/TxChecks'
import useDecodeTx from '@/hooks/useDecodeTx'
Expand All @@ -33,6 +32,7 @@ import { isWalletRejection } from '@/utils/wallets'
import WalletRejectionError from '@/components/tx/SignOrExecuteForm/WalletRejectionError'

import commonCss from '@/components/tx-flow/common/styles.module.css'
import { BlockaidBalanceChanges } from '@/components/tx/security/blockaid/BlockaidBalanceChange'
import NetworkWarning from '@/components/new-safe/create/NetworkWarning'

export function RecoverAccountFlowReview({ params }: { params: RecoverAccountFlowProps }): ReactElement | null {
Expand Down Expand Up @@ -129,7 +129,7 @@ export function RecoverAccountFlowReview({ params }: { params: RecoverAccountFlo

<DecodedTx tx={safeTx} decodedData={decodedData} />

<RedefineBalanceChanges />
<BlockaidBalanceChanges />
</TxCard>

<TxChecks executionOwner={safe.owners[0].value} />
Expand Down
4 changes: 2 additions & 2 deletions src/components/tx-flow/flows/SignMessage/SignMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ import { trackEvent } from '@/services/analytics'
import { TX_EVENTS, TX_TYPES } from '@/services/analytics/events/transactions'
import { SafeTxContext } from '../../SafeTxProvider'
import RiskConfirmationError from '@/components/tx/SignOrExecuteForm/RiskConfirmationError'
import { Redefine } from '@/components/tx/security/redefine'
import { TxSecurityContext } from '@/components/tx/security/shared/TxSecurityContext'
import { isBlindSigningPayload, isEIP712TypedData } from '@/utils/safe-messages'
import ApprovalEditor from '@/components/tx/ApprovalEditor'
Expand All @@ -55,6 +54,7 @@ import { AppRoutes } from '@/config/routes'
import { useRouter } from 'next/router'
import MsgShareLink from '@/components/safe-messages/MsgShareLink'
import LinkIcon from '@/public/images/messages/link.svg'
import { Blockaid } from '@/components/tx/security/blockaid'
import CheckWallet from '@/components/common/CheckWallet'
import NetworkWarning from '@/components/new-safe/create/NetworkWarning'

Expand Down Expand Up @@ -317,7 +317,7 @@ const SignMessage = ({ message, safeAppId, requestId }: ProposeProps | ConfirmPr
</Accordion>

<Box sx={{ '&:not(:empty)': { mt: 2 } }}>
<Redefine />
<Blockaid />
</Box>
</CardContent>
</TxCard>
Expand Down
7 changes: 0 additions & 7 deletions src/components/tx/SignOrExecuteForm/TxChecks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { TxSimulation, TxSimulationMessage } from '@/components/tx/security/tend
import TxCard from '@/components/tx-flow/common/TxCard'
import { SafeTxContext } from '@/components/tx-flow/SafeTxProvider'
import { Box, Typography } from '@mui/material'
import { Redefine, RedefineMessage } from '@/components/tx/security/redefine'

import css from './styles.module.css'

Expand All @@ -29,12 +28,6 @@ const TxChecks = ({ executionOwner }: { executionOwner?: string }): ReactElement
<Box className={css.mobileTxCheckMessages}>
<TxSimulationMessage />
</Box>

<Redefine />

<Box className={css.mobileTxCheckMessages}>
<RedefineMessage />
</Box>
</TxCard>
)
}
Expand Down
10 changes: 5 additions & 5 deletions src/components/tx/SignOrExecuteForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ import TxCard from '@/components/tx-flow/common/TxCard'
import ConfirmationTitle, { ConfirmationTitleTypes } from '@/components/tx/SignOrExecuteForm/ConfirmationTitle'
import { useAppSelector } from '@/store'
import { selectSettings } from '@/store/settingsSlice'
import { RedefineBalanceChanges } from '../security/redefine/RedefineBalanceChange'
import UnknownContractError from './UnknownContractError'
import RiskConfirmationError from './RiskConfirmationError'
import useDecodeTx from '@/hooks/useDecodeTx'
import { ErrorBoundary } from '@sentry/react'
import ApprovalEditor from '../ApprovalEditor'
Expand All @@ -34,6 +32,9 @@ import SwapOrderConfirmationView from '@/features/swap/components/SwapOrderConfi
import { isSettingTwapFallbackHandler } from '@/features/swap/helpers/utils'
import { TwapFallbackHandlerWarning } from '@/features/swap/components/TwapFallbackHandlerWarning'
import useIsSafeOwner from '@/hooks/useIsSafeOwner'
import { BlockaidBalanceChanges } from '../security/blockaid/BlockaidBalanceChange'
import { Blockaid } from '../security/blockaid'

import TxData from '@/components/transactions/TxDetails/TxData'
import { useApprovalInfos } from '../ApprovalEditor/hooks/useApprovalInfos'

Expand Down Expand Up @@ -175,8 +176,7 @@ export const SignOrExecuteForm = ({
/>
</ErrorBoundary>
)}

{!isCounterfactualSafe && !props.isRejection && <RedefineBalanceChanges />}
{!isCounterfactualSafe && !props.isRejection && <BlockaidBalanceChanges />}
</TxCard>

{!isCounterfactualSafe && !props.isRejection && <TxChecks />}
Expand All @@ -201,7 +201,7 @@ export const SignOrExecuteForm = ({

<UnknownContractError />

<RiskConfirmationError />
<Blockaid />

{isCounterfactualSafe && !isDelegate && (
<CounterfactualForm {...props} safeTx={safeTx} isCreation={isCreation} onSubmit={onFormSubmit} onlyExecute />
Expand Down
2 changes: 0 additions & 2 deletions src/components/tx/security/SecurityWarnings.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { RedefineMessage } from './redefine'
import { TxSimulationMessage } from './tenderly'

const SecurityWarnings = () => (
<>
<RedefineMessage />
<TxSimulationMessage />
</>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ import TokenIcon from '@/components/common/TokenIcon'
import useBalances from '@/hooks/useBalances'
import useChainId from '@/hooks/useChainId'
import { useHasFeature } from '@/hooks/useChains'
import { type RedefineModuleResponse } from '@/services/security/modules/RedefineModule'
import { sameAddress } from '@/utils/addresses'
import { FEATURES } from '@/utils/chains'
import { formatVisualAmount } from '@/utils/formatters'
import { Box, Chip, CircularProgress, Grid, SvgIcon, Tooltip, Typography } from '@mui/material'
import { TokenType } from '@safe-global/safe-gateway-typescript-sdk'
import { ErrorBoundary } from '@sentry/react'
Expand All @@ -19,63 +17,75 @@ import ExternalLink from '@/components/common/ExternalLink'
import { REDEFINE_ARTICLE } from '@/config/constants'

import css from './styles.module.css'
import type {
AssetDiff,
Erc1155Diff,
Erc1155TokenDetails,
Erc20Diff,
Erc721Diff,
Erc721TokenDetails,
GeneralAssetDiff,
NativeDiff,
} from '@/services/security/modules/BlockaidModule/types'
import { formatAmount } from '@/utils/formatNumber'

const FungibleBalanceChange = ({
change,
}: {
change: NonNullable<RedefineModuleResponse['balanceChange']>['in' | 'out'][number] & { type: 'ERC20' | 'NATIVE' }
}) => {
const FungibleBalanceChange = ({ change, asset }: { asset: AssetDiff['asset']; change: Erc20Diff | NativeDiff }) => {
const { balances } = useBalances()

const logoUri = balances.items.find((item) => {
return change.type === 'NATIVE'
? item.tokenInfo.type === TokenType.NATIVE_TOKEN
: sameAddress(item.tokenInfo.address, change.address)
})?.tokenInfo.logoUri
const logoUri =
asset.logo_url ??
balances.items.find((item) => {
return asset.type === 'NATIVE'
? item.tokenInfo.type === TokenType.NATIVE_TOKEN
: sameAddress(item.tokenInfo.address, asset.address)
})?.tokenInfo.logoUri

return (
<>
<Typography variant="body2" mx={1}>
{formatVisualAmount(change.amount.value, change.decimals)}
{change.value ? formatAmount(change.value) : 'unknown'}
</Typography>
<TokenIcon size={16} logoUri={logoUri} tokenSymbol={change.symbol} />
<TokenIcon size={16} logoUri={logoUri} tokenSymbol={asset.symbol} />
<Typography variant="body2" fontWeight={700} display="inline" ml={0.5}>
{change.symbol}
{asset.symbol}
</Typography>
<span style={{ margin: 'auto' }} />
<Chip className={css.categoryChip} label={change.type} />
<Chip className={css.categoryChip} label={asset.type} />
</>
)
}

const NFTBalanceChange = ({
change,
asset,
}: {
change: NonNullable<RedefineModuleResponse['balanceChange']>['in' | 'out'][number] & { type: 'ERC721' }
asset: Erc721TokenDetails | Erc1155TokenDetails
change: Erc721Diff | Erc1155Diff
}) => {
const chainId = useChainId()

return (
<>
{change.symbol ? (
{asset.symbol ? (
<Typography variant="body2" fontWeight={700} display="inline" ml={1}>
{change.symbol}
{asset.symbol}
</Typography>
) : (
<Typography variant="body2" ml={1}>
<EthHashInfo
address={change.address}
address={asset.address}
chainId={chainId}
showCopyButton={false}
showPrefix={false}
hasExplorer
showAvatar={false}
customAvatar={asset.logo_url}
showAvatar={!!asset.logo_url}
avatarSize={16}
shortAddress
/>
</Typography>
)}
<Typography variant="subtitle2" className={css.nftId} ml={1}>
#{change.tokenId}
#{Number(change.token_id)}
</Typography>
<span style={{ margin: 'auto' }} />
<Chip className={css.categoryChip} label="NFT" />
Expand All @@ -84,27 +94,36 @@ const NFTBalanceChange = ({
}

const BalanceChange = ({
change,
asset,
positive = false,
diff,
}: {
change: NonNullable<RedefineModuleResponse['balanceChange']>['in' | 'out'][number]
asset: NonNullable<AssetDiff['asset']>
positive?: boolean
diff: GeneralAssetDiff
}) => {
return (
<Grid item xs={12} md={12}>
<Box className={css.balanceChange}>
{positive ? <ArrowDownwardIcon /> : <ArrowOutwardIcon />}
{change.type === 'ERC721' ? <NFTBalanceChange change={change} /> : <FungibleBalanceChange change={change} />}
{asset.type === 'ERC721' || asset.type === 'ERC1155' ? (
<NFTBalanceChange asset={asset} change={diff as Erc721Diff | Erc1155Diff} />
) : (
<FungibleBalanceChange asset={asset} change={diff as NativeDiff | Erc20Diff} />
)}
</Box>
</Grid>
)
}

const BalanceChanges = () => {
const { balanceChange, isLoading } = useContext(TxSecurityContext)
const totalBalanceChanges = balanceChange ? balanceChange.in.length + balanceChange.out.length : 0
const { blockaidResponse } = useContext(TxSecurityContext)
const { isLoading, balanceChange, error } = blockaidResponse ?? {}

const totalBalanceChanges = balanceChange
? balanceChange.reduce((prev, current) => prev + current.in.length + current.out.length, 0)
: 0

if (isLoading && !balanceChange) {
if (isLoading) {
return (
<div className={css.loader}>
<CircularProgress
Expand All @@ -119,7 +138,13 @@ const BalanceChanges = () => {
</div>
)
}

if (error) {
return (
<Typography variant="body2" color="text.secondary" justifySelf="flex-end">
Could not calculate balance changes.
</Typography>
)
}
if (totalBalanceChanges === 0) {
return (
<Typography variant="body2" color="text.secondary" justifySelf="flex-end">
Expand All @@ -131,18 +156,22 @@ const BalanceChanges = () => {
return (
<Grid container className={css.balanceChanges}>
<>
{balanceChange?.in.map((change, idx) => (
<BalanceChange change={change} key={idx} positive />
))}
{balanceChange?.out.map((change, idx) => (
<BalanceChange change={change} key={idx} />
{balanceChange?.map((change, assetIdx) => (
<>
{change.in.map((diff, changeIdx) => (
<BalanceChange key={`${assetIdx}-in-${changeIdx}`} asset={change.asset} positive diff={diff} />
))}
{change.out.map((diff, changeIdx) => (
<BalanceChange key={`${assetIdx}-out-${changeIdx}`} asset={change.asset} diff={diff} />
))}
</>
))}
</>
</Grid>
)
}

export const RedefineBalanceChanges = () => {
export const BlockaidBalanceChanges = () => {
const isFeatureEnabled = useHasFeature(FEATURES.RISK_MITIGATION)

if (!isFeatureEnabled) {
Expand Down
14 changes: 14 additions & 0 deletions src/components/tx/security/blockaid/BlockaidHint.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { type SecuritySeverity } from '@/services/security/modules/types'
import { List, ListItem, Typography } from '@mui/material'

export const BlockaidHint = ({ severity, warnings }: { severity: SecuritySeverity; warnings: string[] }) => {
return (
<List sx={{ listStyle: 'disc', pl: 2, '& li:last-child': { m: 0 } }}>
{warnings.map((warning) => (
<ListItem key={warning} disablePadding sx={{ display: 'list-item', mb: 1 }}>
<Typography variant="body2">{warning}</Typography>
</ListItem>
))}
</List>
)
}
Loading

0 comments on commit ab3871b

Please sign in to comment.