diff --git a/src/components/tx/ApprovalEditor/ApprovalEditor.test.tsx b/src/components/tx/ApprovalEditor/ApprovalEditor.test.tsx index 71897d7606..993deb5bba 100644 --- a/src/components/tx/ApprovalEditor/ApprovalEditor.test.tsx +++ b/src/components/tx/ApprovalEditor/ApprovalEditor.test.tsx @@ -24,7 +24,7 @@ describe('ApprovalEditor', () => { expect(result.container).toBeEmptyDOMElement() }) - it.skip('renders an error', async () => { + it('renders an error', async () => { jest .spyOn(approvalInfos, 'useApprovalInfos') .mockReturnValue([undefined, new Error('Error parsing approvals'), false]) @@ -35,7 +35,7 @@ describe('ApprovalEditor', () => { expect(await result.queryByText('Error while decoding approval transactions.')).toBeInTheDocument() }) - it.skip('renders a loading skeleton', async () => { + it('renders a loading skeleton', async () => { jest.spyOn(approvalInfos, 'useApprovalInfos').mockReturnValue([undefined, undefined, true]) const mockSafeTx = createMockSafeTransaction({ to: '0x1', data: '0x', operation: OperationType.DelegateCall }) diff --git a/src/components/tx/ApprovalEditor/hooks/useApprovalInfos.ts b/src/components/tx/ApprovalEditor/hooks/useApprovalInfos.ts index 2479996e16..664c2220ea 100644 --- a/src/components/tx/ApprovalEditor/hooks/useApprovalInfos.ts +++ b/src/components/tx/ApprovalEditor/hooks/useApprovalInfos.ts @@ -6,6 +6,7 @@ import { type SafeTransaction } from '@safe-global/safe-core-sdk-types' import { type TokenInfo } from '@safe-global/safe-gateway-typescript-sdk' import { ethers } from 'ethers' import { PSEUDO_APPROVAL_VALUES } from '../utils/approvals' +import { useMemo } from 'react' export type ApprovalInfo = { tokenInfo: (Omit & { logoUri?: string }) | undefined @@ -17,22 +18,28 @@ export type ApprovalInfo = { const ApprovalModuleInstance = new ApprovalModule() -export const useApprovalInfos = (safeTransaction: SafeTransaction | undefined) => { +export const useApprovalInfos = ( + safeTransaction: SafeTransaction | undefined, +): [ApprovalInfo[] | undefined, Error | undefined, boolean] => { const { balances } = useBalances() + const approvals = useMemo(() => { + if (!safeTransaction) return - return useAsync( - async () => { - if (!safeTransaction) return + return ApprovalModuleInstance.scanTransaction({ safeTransaction }) + }, [safeTransaction]) - const approvals = await ApprovalModuleInstance.scanTransaction({ safeTransaction }) + const hasApprovalSignatures = !!approvals && !!approvals.payload && approvals.payload.length > 0 - if (!approvals || !approvals.payload || approvals.payload.length === 0) return Promise.resolve([]) + const [approvalInfos, error, loading] = useAsync( + async () => { + if (!hasApprovalSignatures) return return Promise.all( approvals.payload.map(async (approval) => { let tokenInfo: Omit | undefined = balances.items.find( (item) => item.tokenInfo.address === approval.tokenAddress, )?.tokenInfo + if (!tokenInfo) { tokenInfo = await getERC20TokenInfoOnChain(approval.tokenAddress) } @@ -45,7 +52,9 @@ export const useApprovalInfos = (safeTransaction: SafeTransaction | undefined) = }), ) }, - [safeTransaction, balances.items.length], + [hasApprovalSignatures, balances.items.length], false, // Do not clear data on balance updates ) + + return [hasApprovalSignatures ? approvalInfos : [], error, loading] } diff --git a/src/components/tx/ApprovalEditor/index.tsx b/src/components/tx/ApprovalEditor/index.tsx index b9001e5c09..0938b743ae 100644 --- a/src/components/tx/ApprovalEditor/index.tsx +++ b/src/components/tx/ApprovalEditor/index.tsx @@ -1,4 +1,4 @@ -import { Box, Divider, SvgIcon, Typography } from '@mui/material' +import { Alert, Box, Divider, Skeleton, SvgIcon, Typography } from '@mui/material' import { type MetaTransactionData, type SafeTransaction } from '@safe-global/safe-core-sdk-types' import css from './styles.module.css' import { ApprovalEditorForm } from './ApprovalEditorForm' @@ -32,9 +32,9 @@ export const ApprovalEditor = ({ safeTransaction: SafeTransaction | undefined updateTransaction?: (txs: MetaTransactionData[]) => void }) => { - const [readableApprovals] = useApprovalInfos(safeTransaction) + const [readableApprovals, error, loading] = useApprovalInfos(safeTransaction) - if (!readableApprovals || readableApprovals?.length === 0 || !safeTransaction) { + if (readableApprovals?.length === 0 || !safeTransaction) { return null } @@ -52,7 +52,11 @@ export const ApprovalEditor = ({ return ( - {isReadOnly ? ( + {error ? ( + <Alert severity="error">Error while decoding approval transactions.</Alert> + ) : loading || !readableApprovals ? ( + <Skeleton variant="rounded" height={100} data-testid="approval-editor-loading" /> + ) : isReadOnly ? ( <Approvals approvalInfos={readableApprovals} /> ) : ( <ApprovalEditorForm approvalInfos={readableApprovals} updateApprovals={updateApprovals} /> diff --git a/src/services/security/modules/ApprovalModule/index.ts b/src/services/security/modules/ApprovalModule/index.ts index a14b9ddfac..3fc3238f6d 100644 --- a/src/services/security/modules/ApprovalModule/index.ts +++ b/src/services/security/modules/ApprovalModule/index.ts @@ -35,7 +35,7 @@ export class ApprovalModule implements SecurityModule<ApprovalModuleRequest, App return [] } - async scanTransaction(request: ApprovalModuleRequest): Promise<SecurityResponse<ApprovalModuleResponse>> { + scanTransaction(request: ApprovalModuleRequest): SecurityResponse<ApprovalModuleResponse> { const { safeTransaction } = request const safeTxData = safeTransaction.data.data const approvalInfos: Approval[] = [] diff --git a/src/services/security/modules/types.ts b/src/services/security/modules/types.ts index 4c46aafac3..415fe2cd0a 100644 --- a/src/services/security/modules/types.ts +++ b/src/services/security/modules/types.ts @@ -17,5 +17,5 @@ export type SecurityResponse<Res> = } export interface SecurityModule<Req, Res> { - scanTransaction(request: Req): Promise<SecurityResponse<Res>> + scanTransaction(request: Req): Promise<SecurityResponse<Res>> | SecurityResponse<Res> }