-
Notifications
You must be signed in to change notification settings - Fork 411
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
327 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
src/components/tx-flow/flows/EnableSafenet/ReviewEnableSafenet.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { useContext, useEffect } from 'react' | ||
import { Typography } from '@mui/material' | ||
import SignOrExecuteForm from '@/components/tx/SignOrExecuteForm' | ||
import EthHashInfo from '@/components/common/EthHashInfo' | ||
import { Errors, logError } from '@/services/exceptions' | ||
import { createEnableGuardTx, createMultiSendCallOnlyTx } from '@/services/tx/tx-sender' | ||
import { type EnableSafenetFlowProps } from '.' | ||
import { SafeTxContext } from '@/components/tx-flow/SafeTxProvider' | ||
import type { MetaTransactionData, SafeTransaction } from '@safe-global/safe-core-sdk-types' | ||
import { OperationType } from '@safe-global/safe-core-sdk-types' | ||
import { ERC20__factory } from '@/types/contracts' | ||
import { UNLIMITED_APPROVAL_AMOUNT } from '@/utils/tokens' | ||
|
||
const ERC20_INTERFACE = ERC20__factory.createInterface() | ||
|
||
export const ReviewEnableSafenet = ({ params }: { params: EnableSafenetFlowProps }) => { | ||
const { setSafeTx, safeTxError, setSafeTxError } = useContext(SafeTxContext) | ||
|
||
useEffect(() => { | ||
async function getTxs(): Promise<SafeTransaction> { | ||
if (params.tokensForPresetAllowances.length === 0) { | ||
return await createEnableGuardTx(params.guardAddress) | ||
} | ||
|
||
const txs: MetaTransactionData[] = [(await createEnableGuardTx(params.guardAddress)).data] | ||
params.tokensForPresetAllowances.forEach((tokenAddress) => { | ||
txs.push({ | ||
to: tokenAddress, | ||
data: ERC20_INTERFACE.encodeFunctionData('approve', [params.guardAddress, UNLIMITED_APPROVAL_AMOUNT]), | ||
value: '0', | ||
operation: OperationType.Call, | ||
}) | ||
}) | ||
|
||
return createMultiSendCallOnlyTx(txs) | ||
} | ||
|
||
getTxs().then(setSafeTx).catch(setSafeTxError) | ||
}, [setSafeTx, setSafeTxError, params.guardAddress, params.tokensForPresetAllowances]) | ||
|
||
useEffect(() => { | ||
if (safeTxError) { | ||
logError(Errors._807, safeTxError.message) | ||
} | ||
}, [safeTxError]) | ||
|
||
return ( | ||
<SignOrExecuteForm> | ||
<Typography sx={({ palette }) => ({ color: palette.primary.light })}>Transaction guard</Typography> | ||
|
||
<EthHashInfo address={params.guardAddress} showCopyButton hasExplorer shortAddress={false} /> | ||
|
||
<Typography my={2}> | ||
Once the transaction guard has been enabled, SafeNet will be enabled for your Safe. | ||
</Typography> | ||
</SignOrExecuteForm> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import TxLayout from '@/components/tx-flow/common/TxLayout' | ||
import { ReviewEnableSafenet } from './ReviewEnableSafenet' | ||
|
||
export type EnableSafenetFlowProps = { | ||
guardAddress: string | ||
tokensForPresetAllowances: string[] | ||
} | ||
|
||
const EnableSafenetFlow = ({ guardAddress, tokensForPresetAllowances }: EnableSafenetFlowProps) => { | ||
return ( | ||
<TxLayout title="Confirm transaction" subtitle="Enable SafeNet"> | ||
<ReviewEnableSafenet params={{ guardAddress, tokensForPresetAllowances }} /> | ||
</TxLayout> | ||
) | ||
} | ||
|
||
export { EnableSafenetFlow } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
import type { NextPage } from 'next' | ||
import Head from 'next/head' | ||
import { Button, CircularProgress, Grid, Paper, SvgIcon, Tooltip, Typography } from '@mui/material' | ||
import { QueryStatus } from '@reduxjs/toolkit/query' | ||
import InfoIcon from '@/public/images/notifications/info.svg' | ||
|
||
import SettingsHeader from '@/components/settings/SettingsHeader' | ||
import useSafeInfo from '@/hooks/useSafeInfo' | ||
import { sameAddress } from '@/utils/addresses' | ||
import { useContext, useEffect, useMemo } from 'react' | ||
import { TxModalContext } from '@/components/tx-flow' | ||
import { EnableSafenetFlow } from '@/components/tx-flow/flows/EnableSafenet' | ||
import type { SafenetConfigEntity } from '@/store/safenet' | ||
import { | ||
useGetSafenetConfigQuery, | ||
useLazyGetSafeNetOffchainStatusQuery, | ||
useRegisterSafeNetMutation, | ||
} from '@/store/safenet' | ||
import type { ExtendedSafeInfo } from '@/store/safeInfoSlice' | ||
import { SAFE_FEATURES } from '@safe-global/protocol-kit/dist/src/utils' | ||
import { hasSafeFeature } from '@/utils/safe-versions' | ||
|
||
const isSupportedChain = (chainId: number, safenetConfig: SafenetConfigEntity) => { | ||
return safenetConfig.chains.sources.includes(chainId) | ||
} | ||
|
||
const getSafenetTokensByChain = (chainId: number, safenetConfig: SafenetConfigEntity): string[] => { | ||
const tokenSymbols = Object.keys(safenetConfig.tokens) | ||
|
||
const tokens: string[] = [] | ||
for (const symbol of tokenSymbols) { | ||
const tokenAddress = safenetConfig.tokens[symbol][chainId] | ||
if (tokenAddress) { | ||
tokens.push(tokenAddress) | ||
} | ||
} | ||
|
||
return tokens | ||
} | ||
|
||
const SafeNetContent = ({ safenetConfig, safe }: { safenetConfig: SafenetConfigEntity; safe: ExtendedSafeInfo }) => { | ||
const isVersionWithGuards = hasSafeFeature(SAFE_FEATURES.SAFE_TX_GUARDS, safe.version) | ||
const safenetGuardAddress = safenetConfig.guards[safe.chainId] | ||
const isSafeNetGuardEnabled = isVersionWithGuards && sameAddress(safe.guard?.value, safenetGuardAddress) | ||
const chainSupported = isSupportedChain(Number(safe.chainId), safenetConfig) | ||
const { setTxFlow } = useContext(TxModalContext) | ||
|
||
// Lazy query because running it on unsupported chain throws an error | ||
const [ | ||
triggerGetSafeNetOffchainStatus, | ||
{ | ||
data: safeNetOffchainStatus, | ||
error: safeNetOffchainStatusError, | ||
isLoading: safeNetOffchainStatusLoading, | ||
status: safeNetOffchainStatusStatus, | ||
}, | ||
] = useLazyGetSafeNetOffchainStatusQuery() | ||
|
||
// @ts-expect-error bad types. We don't want 404 to be an error - it just means that the safe is not registered | ||
const offchainLookupError = safeNetOffchainStatusError?.status === 404 ? null : safeNetOffchainStatusError | ||
const registeredOffchainStatus = | ||
!offchainLookupError && sameAddress(safeNetOffchainStatus?.guard, safenetGuardAddress) | ||
|
||
const safenetStatusQueryWorked = | ||
safeNetOffchainStatusStatus === QueryStatus.fulfilled || safeNetOffchainStatusStatus === QueryStatus.rejected | ||
const needsRegistration = safenetStatusQueryWorked && isSafeNetGuardEnabled && !registeredOffchainStatus | ||
const [registerSafeNet, { error: registerSafeNetError }] = useRegisterSafeNetMutation() | ||
const error = offchainLookupError || registerSafeNetError | ||
const safenetAssets = useMemo( | ||
() => getSafenetTokensByChain(Number(safe.chainId), safenetConfig), | ||
[safe.chainId, safenetConfig], | ||
) | ||
|
||
if (error) { | ||
throw error | ||
} | ||
|
||
useEffect(() => { | ||
if (needsRegistration) { | ||
registerSafeNet({ chainId: safe.chainId, safeAddress: safe.address.value }) | ||
} | ||
}, [needsRegistration, registerSafeNet, safe.chainId, safe.address.value]) | ||
|
||
useEffect(() => { | ||
if (chainSupported) { | ||
triggerGetSafeNetOffchainStatus({ chainId: safe.chainId, safeAddress: safe.address.value }) | ||
} | ||
}, [chainSupported, triggerGetSafeNetOffchainStatus, safe.chainId, safe.address.value]) | ||
|
||
switch (true) { | ||
case !chainSupported: | ||
return ( | ||
<Typography> | ||
SafeNet is not supported on this chain. List of supported chains ids:{' '} | ||
{safenetConfig.chains.sources.join(', ')} | ||
</Typography> | ||
) | ||
case !isVersionWithGuards: | ||
return <Typography>Please upgrade your Safe to the latest version to use SafeNet</Typography> | ||
case isSafeNetGuardEnabled: | ||
return <Typography>SafeNet is enabled. Enjoy your unified experience.</Typography> | ||
case !isSafeNetGuardEnabled: | ||
return ( | ||
<div> | ||
<Typography>SafeNet is not enabled. Enable it to enhance your Safe experience.</Typography> | ||
<Button | ||
variant="contained" | ||
onClick={() => | ||
setTxFlow( | ||
<EnableSafenetFlow guardAddress={safenetGuardAddress} tokensForPresetAllowances={safenetAssets} />, | ||
) | ||
} | ||
sx={{ mt: 2 }} | ||
> | ||
Enable | ||
</Button> | ||
</div> | ||
) | ||
case safeNetOffchainStatusLoading: | ||
return <CircularProgress /> | ||
default: | ||
return null | ||
} | ||
} | ||
|
||
const SafeNetPage: NextPage = () => { | ||
const { safe, safeLoaded } = useSafeInfo() | ||
const { data: safenetConfig, isLoading: safenetConfigLoading, error: safenetConfigError } = useGetSafenetConfigQuery() | ||
|
||
if (!safeLoaded || safenetConfigLoading) { | ||
return <CircularProgress /> | ||
} | ||
|
||
if (safenetConfigError) { | ||
return <Typography>Error loading SafeNet config</Typography> | ||
} | ||
|
||
if (!safenetConfig) { | ||
// Should never happen, making TS happy | ||
return <Typography>No SafeNet config found</Typography> | ||
} | ||
|
||
const safenetContent = <SafeNetContent safenetConfig={safenetConfig} safe={safe} /> | ||
|
||
return ( | ||
<> | ||
<Head> | ||
<title>{'Safe{Wallet} – Settings – SafeNet'}</title> | ||
</Head> | ||
|
||
<SettingsHeader /> | ||
|
||
<main> | ||
<Paper data-testid="setup-section" sx={{ p: 4, mb: 2 }}> | ||
<Grid container spacing={3}> | ||
<Grid item lg={4} xs={12}> | ||
<Typography variant="h4" fontWeight={700}> | ||
<Tooltip | ||
placement="top" | ||
title="SafeNet enhances your Safe experience by providing additional security features." | ||
> | ||
<span> | ||
SafeNet Status | ||
<SvgIcon | ||
component={InfoIcon} | ||
inheritViewBox | ||
fontSize="small" | ||
color="border" | ||
sx={{ verticalAlign: 'middle', ml: 0.5 }} | ||
/> | ||
</span> | ||
</Tooltip> | ||
</Typography> | ||
</Grid> | ||
|
||
<Grid item xs> | ||
{safenetContent} | ||
</Grid> | ||
</Grid> | ||
</Paper> | ||
</main> | ||
</> | ||
) | ||
} | ||
|
||
export default SafeNetPage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' | ||
|
||
import { SAFENET_API_URL } from '@/config/constants' | ||
|
||
export type SafenetSafeEntity = { | ||
safe: string | ||
chainId: number | ||
guard: string | ||
} | ||
|
||
export type SafenetConfigEntity = { | ||
chains: { | ||
sources: number[] | ||
destinations: number[] | ||
} | ||
guards: Record<string, string> | ||
tokens: Record<string, Record<string, string>> | ||
} | ||
|
||
export const safenetApi = createApi({ | ||
reducerPath: 'safenetApi', | ||
baseQuery: fetchBaseQuery({ baseUrl: `${SAFENET_API_URL}/safenet` }), | ||
tagTypes: ['SafeNetOffchainStatus'], | ||
endpoints: (builder) => ({ | ||
getSafenetConfig: builder.query<SafenetConfigEntity, void>({ | ||
query: () => `/config/`, | ||
}), | ||
getSafeNetOffchainStatus: builder.query<SafenetSafeEntity, { chainId: string; safeAddress: string }>({ | ||
query: ({ chainId, safeAddress }) => `/safes/${chainId}/${safeAddress}`, | ||
providesTags: (_, __, arg) => [{ type: 'SafeNetOffchainStatus', id: arg.safeAddress }], | ||
}), | ||
registerSafeNet: builder.mutation<boolean, { chainId: string; safeAddress: string }>({ | ||
query: ({ chainId, safeAddress }) => ({ | ||
url: `/register`, | ||
method: 'POST', | ||
body: { | ||
chainId: Number(chainId), | ||
safe: safeAddress, | ||
}, | ||
}), | ||
invalidatesTags: (_, __, arg) => [{ type: 'SafeNetOffchainStatus', id: arg.safeAddress }], | ||
}), | ||
}), | ||
}) | ||
|
||
export const { useLazyGetSafeNetOffchainStatusQuery, useRegisterSafeNetMutation, useGetSafenetConfigQuery } = safenetApi |