From 32c926372af96fe2f0fa6febae8e6bfcd18ed313 Mon Sep 17 00:00:00 2001 From: katspaugh <381895+katspaugh@users.noreply.github.com> Date: Thu, 19 Dec 2024 13:58:55 +0100 Subject: [PATCH] Feat: public transaction notes --- .../transactions/TxDetails/TxNote.tsx | 44 +++++++++++++++++++ .../transactions/TxDetails/index.tsx | 3 ++ .../SignOrExecuteForm/SignOrExecuteForm.tsx | 26 ++++++++++- .../tx/SignOrExecuteForm/TxNoteForm.tsx | 39 ++++++++++++++++ src/hooks/useTransactionType.tsx | 16 +++---- 5 files changed, 119 insertions(+), 9 deletions(-) create mode 100644 src/components/transactions/TxDetails/TxNote.tsx create mode 100644 src/components/tx/SignOrExecuteForm/TxNoteForm.tsx diff --git a/src/components/transactions/TxDetails/TxNote.tsx b/src/components/transactions/TxDetails/TxNote.tsx new file mode 100644 index 0000000000..e174acc8f6 --- /dev/null +++ b/src/components/transactions/TxDetails/TxNote.tsx @@ -0,0 +1,44 @@ +import { DataRow } from '@/components/common/Table/DataRow' +import useAsync from '@/hooks/useAsync' +import { useCurrentChain } from '@/hooks/useChains' +import { isMultisigDetailedExecutionInfo } from '@/utils/transaction-guards' +import { Box, Divider } from '@mui/material' +import { TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk' + +const TxNote = ({ txDetails }: { txDetails: TransactionDetails }) => { + const currentChain = useCurrentChain() + const txService = currentChain?.transactionService + + let safeTxHash = '' + if (isMultisigDetailedExecutionInfo(txDetails.detailedExecutionInfo)) { + safeTxHash = txDetails.detailedExecutionInfo?.safeTxHash + } + + const [data] = useAsync(() => { + if (!safeTxHash || !txService) return + return fetch(`${txService}/api/v1/multisig-transactions/${safeTxHash}`).then((res) => res.json()) + }, [safeTxHash, txService]) + + let note = '' + if (data) { + try { + const origin = JSON.parse(data.origin) + const parsedName = JSON.parse(origin.name) + note = parsedName.note + } catch { + // Ignore, no note + } + } + + return note ? ( + <> + + {note} + + + + + ) : null +} + +export default TxNote diff --git a/src/components/transactions/TxDetails/index.tsx b/src/components/transactions/TxDetails/index.tsx index 412df2815d..ac3211fe00 100644 --- a/src/components/transactions/TxDetails/index.tsx +++ b/src/components/transactions/TxDetails/index.tsx @@ -36,6 +36,7 @@ import { FEATURES } from '@/utils/chains' import { useGetTransactionDetailsQuery } from '@/store/api/gateway' import { asError } from '@/services/exceptions/utils' import { POLLING_INTERVAL } from '@/config/constants' +import TxNote from './TxNote' export const NOT_AVAILABLE = 'n/a' @@ -82,6 +83,8 @@ const TxDetailsBlock = ({ txSummary, txDetails }: TxDetailsProps): ReactElement <> {/* /Details */}
+ +
diff --git a/src/components/tx/SignOrExecuteForm/SignOrExecuteForm.tsx b/src/components/tx/SignOrExecuteForm/SignOrExecuteForm.tsx index f5944f1a52..d0f22c7c79 100644 --- a/src/components/tx/SignOrExecuteForm/SignOrExecuteForm.tsx +++ b/src/components/tx/SignOrExecuteForm/SignOrExecuteForm.tsx @@ -41,6 +41,7 @@ import ConfirmationView from '../confirmation-views' import { SignerForm } from './SignerForm' import { useSigner } from '@/hooks/wallets/useWallet' import { isNestedConfirmationTxInfo } from '@/utils/transaction-guards' +import TxNoteForm from './TxNoteForm' export type SubmitCallback = (txId: string, isExecuted?: boolean) => void @@ -142,6 +143,7 @@ export const SignOrExecuteForm = ({ isCreation?: boolean txDetails?: TransactionDetails }): ReactElement => { + const [customOrigin, setCustomOrigin] = useState(props.origin) const { transactionExecution } = useAppSelector(selectSettings) const [shouldExecute, setShouldExecute] = useState(transactionExecution) const isNewExecutableTx = useImmediatelyExecutable() && isCreation @@ -204,6 +206,19 @@ export const SignOrExecuteForm = ({ [onFormSubmit], ) + const onNoteSubmit = useCallback( + (note: string) => { + const originalOrigin = props.origin ? JSON.parse(props.origin) : { url: location.origin } + setCustomOrigin( + JSON.stringify({ + ...originalOrigin, + name: JSON.stringify({ note }), + }), + ) + }, + [setCustomOrigin, props.origin], + ) + return ( <> @@ -230,6 +245,8 @@ export const SignOrExecuteForm = ({ {!isCounterfactualSafe && !props.isRejection && } + {isCreation && } + @@ -264,7 +281,13 @@ export const SignOrExecuteForm = ({ )} {!isCounterfactualSafe && willExecute && !isProposing && ( - + )} {!isCounterfactualSafe && willExecuteThroughRole && ( void }) => { + const [note, setNote] = useState('') + + const onFormSubmit = (e: React.FormEvent) => { + e.preventDefault() + const formData = new FormData(e.currentTarget) + const value = formData.get('note') as string + if (value) { + onSubmit(value) + setNote(value) + } + } + + return ( + +
+ Add a note + + + + + + Attention: transaction notes are public + + {note && Note added} + + + +
+ ) +} + +export default TxNoteForm diff --git a/src/hooks/useTransactionType.tsx b/src/hooks/useTransactionType.tsx index fa2f74d48b..066c22a82b 100644 --- a/src/hooks/useTransactionType.tsx +++ b/src/hooks/useTransactionType.tsx @@ -111,7 +111,14 @@ export const getTransactionType = (tx: TransactionSummary, addressBook: AddressB } } case TransactionInfoType.CUSTOM: { - if (isMultiSendTxInfo(tx.txInfo) && !tx.safeAppInfo) { + if (tx.safeAppInfo) { + return { + icon: tx.safeAppInfo.logoUri, + text: tx.safeAppInfo.name, + } + } + + if (isMultiSendTxInfo(tx.txInfo)) { return { icon: , text: 'Batch', @@ -145,13 +152,6 @@ export const getTransactionType = (tx: TransactionSummary, addressBook: AddressB } } default: { - if (tx.safeAppInfo) { - return { - icon: tx.safeAppInfo.logoUri, - text: tx.safeAppInfo.name, - } - } - return { icon: '/images/transactions/custom.svg', text: addressBookName || 'Contract interaction',