Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PoC: public transaction notes #4693

Draft
wants to merge 5 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 type { 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())
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using the tx-service directly here because CGW doesn't mirror the origin field. It can be easily added there.

}, [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
Loading