Skip to content

Commit

Permalink
Feat: public transaction notes
Browse files Browse the repository at this point in the history
  • Loading branch information
katspaugh committed Dec 19, 2024
1 parent 011e808 commit 32c9263
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 9 deletions.
44 changes: 44 additions & 0 deletions src/components/transactions/TxDetails/TxNote.tsx
Original file line number Diff line number Diff line change
@@ -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 ? (
<>
<Box m={2} py={1}>
<DataRow title="Proposer note:">{note}</DataRow>
</Box>

<Divider sx={{ mb: 2 }} />
</>
) : null
}

export default TxNote
3 changes: 3 additions & 0 deletions src/components/transactions/TxDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -82,6 +83,8 @@ const TxDetailsBlock = ({ txSummary, txDetails }: TxDetailsProps): ReactElement
<>
{/* /Details */}
<div className={`${css.details} ${isUnsigned ? css.noSigners : ''}`}>
<TxNote txDetails={txDetails} />

<div className={css.shareLink}>
<TxShareLink id={txSummary.id} />
</div>
Expand Down
26 changes: 25 additions & 1 deletion src/components/tx/SignOrExecuteForm/SignOrExecuteForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -142,6 +143,7 @@ export const SignOrExecuteForm = ({
isCreation?: boolean
txDetails?: TransactionDetails
}): ReactElement => {
const [customOrigin, setCustomOrigin] = useState<string | undefined>(props.origin)
const { transactionExecution } = useAppSelector(selectSettings)
const [shouldExecute, setShouldExecute] = useState<boolean>(transactionExecution)
const isNewExecutableTx = useImmediatelyExecutable() && isCreation
Expand Down Expand Up @@ -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 (
<>
<TxCard>
Expand All @@ -230,6 +245,8 @@ export const SignOrExecuteForm = ({

{!isCounterfactualSafe && !props.isRejection && <TxChecks />}

{isCreation && <TxNoteForm onSubmit={onNoteSubmit} />}

<SignerForm willExecute={willExecute} />

<TxCard>
Expand Down Expand Up @@ -264,7 +281,13 @@ export const SignOrExecuteForm = ({
<CounterfactualForm {...props} safeTx={safeTx} isCreation={isCreation} onSubmit={onFormSubmit} onlyExecute />
)}
{!isCounterfactualSafe && willExecute && !isProposing && (
<ExecuteForm {...props} safeTx={safeTx} isCreation={isCreation} onSubmit={onFormSubmit} />
<ExecuteForm
{...props}
origin={customOrigin}
safeTx={safeTx}
isCreation={isCreation}
onSubmit={onFormSubmit}
/>
)}
{!isCounterfactualSafe && willExecuteThroughRole && (
<ExecuteThroughRoleForm
Expand All @@ -278,6 +301,7 @@ export const SignOrExecuteForm = ({
{!isCounterfactualSafe && !willExecute && !willExecuteThroughRole && !isProposing && (
<SignForm
{...props}
origin={customOrigin}
safeTx={safeTx}
isBatchable={isBatchable}
isCreation={isCreation}
Expand Down
39 changes: 39 additions & 0 deletions src/components/tx/SignOrExecuteForm/TxNoteForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useState } from 'react'
import { Button, Stack, TextField, Typography } from '@mui/material'
import TxCard from '@/components/tx-flow/common/TxCard'

const TxNoteForm = ({ onSubmit }: { onSubmit: (note: string) => void }) => {
const [note, setNote] = useState('')

const onFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
const value = formData.get('note') as string
if (value) {
onSubmit(value)
setNote(value)
}
}

return (
<TxCard>
<form onSubmit={onFormSubmit}>
<Typography variant="h6">Add a note</Typography>

<TextField name="note" label="Transaction description" fullWidth margin="normal" disabled={!!note} />

<Stack direction="row" spacing={2} alignItems="center" justifyContent="flex-end" sx={{ mt: 1 }}>
<Typography variant="caption" flex={1}>
Attention: transaction notes are public
</Typography>
{note && <Typography variant="body2">Note added</Typography>}
<Button variant="outlined" type="submit" disabled={!!note}>
Add note
</Button>
</Stack>
</form>
</TxCard>
)
}

export default TxNoteForm
16 changes: 8 additions & 8 deletions src/hooks/useTransactionType.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: <SvgIcon component={BatchIcon} inheritViewBox fontSize="small" alt="Batch" />,
text: 'Batch',
Expand Down Expand Up @@ -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',
Expand Down

0 comments on commit 32c9263

Please sign in to comment.