Skip to content

Commit

Permalink
Feat: decode execTransaction SafeTransactions (#4449)
Browse files Browse the repository at this point in the history
This commit adds a new component called ExecTransaction, which is used in the TxData component to display nested transaction details.


Co-authored-by: Tim Schwarz <4171783+tmjssz@users.noreply.github.com>
  • Loading branch information
schmanu and tmjssz authored Oct 31, 2024
1 parent c8b4f51 commit c589bff
Show file tree
Hide file tree
Showing 9 changed files with 354 additions and 125 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Safe__factory } from '@/types/contracts'
import { Skeleton } from '@mui/material'
import { getConfirmationView, type TransactionData } from '@safe-global/safe-gateway-typescript-sdk'
import ErrorMessage from '@/components/tx/ErrorMessage'

import DecodedTx from '@/components/tx/DecodedTx'
import Link from 'next/link'
import { useCurrentChain } from '@/hooks/useChains'
import { AppRoutes } from '@/config/routes'
import { useMemo } from 'react'
import type { SafeTransaction } from '@safe-global/safe-core-sdk-types'
import useAsync from '@/hooks/useAsync'
import ExternalLink from '@/components/common/ExternalLink'
import { NestedTransaction } from '../NestedTransaction'

const safeInterface = Safe__factory.createInterface()

const extractTransactionData = (data: string): SafeTransaction | undefined => {
const params = data ? safeInterface.decodeFunctionData('execTransaction', data) : undefined
if (!params || params.length !== 10) {
return
}

return {
addSignature: () => {},
encodedSignatures: () => params[9],
getSignature: () => undefined,
data: {
to: params[0],
value: params[1],
data: params[2],
operation: params[3],
safeTxGas: params[4],
baseGas: params[5],
gasPrice: params[6],
gasToken: params[7],
refundReceiver: params[8],
nonce: -1,
},
signatures: new Map(),
}
}

export const ExecTransaction = ({ data }: { data?: TransactionData }) => {
const chain = useCurrentChain()

const childSafeTx = useMemo<SafeTransaction | undefined>(
() => (data?.hexData ? extractTransactionData(data.hexData) : undefined),
[data?.hexData],
)

const [decodedNestedTransaction, error] = useAsync(async () => {
if (chain?.chainId && data?.to.value && childSafeTx) {
return await getConfirmationView(
chain.chainId,
data.to.value,
childSafeTx.data.data,
childSafeTx.data.to,
childSafeTx.data.value.toString(),
)
}
}, [chain?.chainId, data?.to.value, childSafeTx])

const decodedNestedTxDataBlock = decodedNestedTransaction ? (
<DecodedTx tx={childSafeTx} showMethodCall decodedData={decodedNestedTransaction} showAdvancedDetails={false} />
) : null

return (
<NestedTransaction txData={data}>
{decodedNestedTxDataBlock ? (
<>
{decodedNestedTxDataBlock}

{chain && data && (
<Link
href={{
pathname: AppRoutes.transactions.history,
query: { safe: `${chain.shortName}:${data.to.value}` },
}}
passHref
legacyBehavior
>
<ExternalLink>Open Safe</ExternalLink>
</Link>
)}
</>
) : error ? (
<ErrorMessage>Could not load details on executed transaction.</ErrorMessage>
) : (
<Skeleton />
)}
</NestedTransaction>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Stack, SvgIcon, Typography } from '@mui/material'
import { type TransactionData } from '@safe-global/safe-gateway-typescript-sdk'

import { Divider } from '@/components/tx/DecodedTx'

import NestedTransactionIcon from '@/public/images/transactions/nestedTx.svg'
import { type ReactElement } from 'react'
import MethodCall from '../DecodedData/MethodCall'
import { MethodDetails } from '../DecodedData/MethodDetails'

export const NestedTransaction = ({
txData,
children,
}: {
txData: TransactionData | undefined
children: ReactElement
}) => {
return (
<Stack spacing={2}>
{txData?.dataDecoded && (
<>
<MethodCall contractAddress={txData.to.value} method={txData.dataDecoded.method} />
<MethodDetails data={txData.dataDecoded} addressInfoIndex={txData.addressInfoIndex} />
</>
)}

<Divider />

<Stack spacing={2}>
<Typography variant="h5" display="flex" alignItems="center" gap={1}>
<SvgIcon component={NestedTransactionIcon} inheritViewBox fontSize="small" /> Nested transaction:
</Typography>
{children}
</Stack>
</Stack>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import useChainId from '@/hooks/useChainId'
import { Safe__factory } from '@/types/contracts'
import { Skeleton } from '@mui/material'
import { type TransactionData } from '@safe-global/safe-gateway-typescript-sdk'
import TxData from '../..'
import ErrorMessage from '@/components/tx/ErrorMessage'

import Link from 'next/link'
import { useCurrentChain } from '@/hooks/useChains'
import { AppRoutes } from '@/config/routes'
import { useGetTransactionDetailsQuery } from '@/store/api/gateway'
import { useMemo } from 'react'
import { skipToken } from '@reduxjs/toolkit/query'
import ExternalLink from '@/components/common/ExternalLink'
import { NestedTransaction } from '../NestedTransaction'

const safeInterface = Safe__factory.createInterface()

export const OnChainConfirmation = ({ data }: { data?: TransactionData }) => {
const chain = useCurrentChain()
const chainId = useChainId()
const signedHash = useMemo(() => {
const params = data?.hexData ? safeInterface.decodeFunctionData('approveHash', data?.hexData) : undefined
if (!params || params.length !== 1 || typeof params[0] !== 'string') {
return
}

return params[0]
}, [data?.hexData])

const { data: nestedTxDetails, error: txDetailsError } = useGetTransactionDetailsQuery(
signedHash
? {
chainId,
txId: signedHash,
}
: skipToken,
)

return (
<NestedTransaction txData={data}>
{nestedTxDetails ? (
<>
<TxData txDetails={nestedTxDetails} trusted imitation={false} />

{chain && data && (
<Link
href={{
pathname: AppRoutes.transactions.tx,
query: {
safe: `${chain?.shortName}:${data?.to.value}`,
id: nestedTxDetails.txId,
},
}}
passHref
legacyBehavior
>
<ExternalLink>Open nested transaction</ExternalLink>
</Link>
)}
</>
) : txDetailsError ? (
<ErrorMessage>Could not load details on hash to approve.</ErrorMessage>
) : (
<Skeleton />
)}
</NestedTransaction>
)
}

This file was deleted.

9 changes: 7 additions & 2 deletions src/components/transactions/TxDetails/TxData/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import SettingsChangeTxInfo from '@/components/transactions/TxDetails/TxData/SettingsChange'
import type { SpendingLimitMethods } from '@/utils/transaction-guards'
import { isOnChainConfirmationTxData, isStakingTxWithdrawInfo } from '@/utils/transaction-guards'
import { isExecTxData, isOnChainConfirmationTxData, isStakingTxWithdrawInfo } from '@/utils/transaction-guards'
import { isStakingTxExitInfo } from '@/utils/transaction-guards'
import {
isCancellationTxInfo,
Expand All @@ -26,7 +26,8 @@ import SwapOrder from '@/features/swap/components/SwapOrder'
import StakingTxDepositDetails from '@/features/stake/components/StakingTxDepositDetails'
import StakingTxExitDetails from '@/features/stake/components/StakingTxExitDetails'
import StakingTxWithdrawDetails from '@/features/stake/components/StakingTxWithdrawDetails'
import { OnChainConfirmation } from './OnChainConfirmation'
import { OnChainConfirmation } from './NestedTransaction/OnChainConfirmation'
import { ExecTransaction } from './NestedTransaction/ExecTransaction'

const TxData = ({
txDetails,
Expand Down Expand Up @@ -82,6 +83,10 @@ const TxData = ({
return <OnChainConfirmation data={txDetails.txData} />
}

if (isExecTxData(txDetails.txData)) {
return <ExecTransaction data={txDetails.txData} />
}

return <DecodedData txData={txDetails.txData} toInfo={toInfo} />
}

Expand Down
Loading

0 comments on commit c589bff

Please sign in to comment.