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 all 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 @@ -32,6 +32,7 @@ import NetworkWarning from '@/components/new-safe/create/NetworkWarning'
import ConfirmationView from '../confirmation-views'
import { SignerForm } from './SignerForm'
import { useSigner } from '@/hooks/wallets/useWallet'
import TxNoteForm from './TxNoteForm'
import { trackTxEvents } from './tracking'

export type SubmitCallback = (txId: string, isExecuted?: boolean) => void
Expand Down Expand Up @@ -64,6 +65,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 @@ -124,6 +126,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 @@ -149,6 +164,8 @@ export const SignOrExecuteForm = ({

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

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

<SignerForm willExecute={willExecute} />

<TxCard>
Expand Down Expand Up @@ -183,7 +200,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 @@ -197,6 +220,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
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,73 @@ exports[`SignOrExecute should display a confirmation screen 1`] = `
</div>
</div>
</div>
<div
class="MuiPaper-root MuiPaper-elevation MuiPaper-rounded MuiPaper-elevation0 MuiCard-root css-1w4z6qv-MuiPaper-root-MuiCard-root"
style="--Paper-shadow: none;"
>
<div
class="MuiCardContent-root cardContent css-1lt5qva-MuiCardContent-root"
>
<form>
<h6
class="MuiTypography-root MuiTypography-h6 css-v1yov6-MuiTypography-root"
>
Add a note
</h6>
<div
class="MuiFormControl-root MuiFormControl-marginNormal MuiFormControl-fullWidth MuiTextField-root css-16bevx5-MuiFormControl-root-MuiTextField-root"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeMedium MuiInputLabel-outlined MuiFormLabel-colorPrimary MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeMedium MuiInputLabel-outlined css-ll8nw6-MuiFormLabel-root-MuiInputLabel-root"
data-shrink="false"
for=":r1:"
id=":r1:-label"
>
Transaction description
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-colorPrimary MuiInputBase-fullWidth MuiInputBase-formControl css-1pfp7cx-MuiInputBase-root-MuiOutlinedInput-root"
>
<input
aria-invalid="false"
class="MuiInputBase-input MuiOutlinedInput-input css-1eoo0u4-MuiInputBase-input-MuiOutlinedInput-input"
id=":r1:"
name="note"
type="text"
/>
<fieldset
aria-hidden="true"
class="MuiOutlinedInput-notchedOutline css-1focg1t-MuiOutlinedInput-notchedOutline"
>
<legend
class="css-81qg8w"
>
<span>
Transaction description
</span>
</legend>
</fieldset>
</div>
</div>
<div
class="MuiStack-root css-mw1pf-MuiStack-root"
>
<span
class="MuiTypography-root MuiTypography-caption css-cmorml-MuiTypography-root"
>
Attention: transaction notes are public
</span>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-outlined MuiButton-outlinedPrimary MuiButton-sizeMedium MuiButton-outlinedSizeMedium MuiButton-colorPrimary MuiButton-root MuiButton-outlined MuiButton-outlinedPrimary MuiButton-sizeMedium MuiButton-outlinedSizeMedium MuiButton-colorPrimary css-vi2kll-MuiButtonBase-root-MuiButton-root"
tabindex="0"
type="submit"
>
Add note
</button>
</div>
</form>
</div>
</div>
<div
class="MuiPaper-root MuiPaper-elevation MuiPaper-rounded MuiPaper-elevation0 MuiCard-root css-1w4z6qv-MuiPaper-root-MuiCard-root"
style="--Paper-shadow: none;"
Expand Down Expand Up @@ -426,6 +493,73 @@ exports[`SignOrExecute should display an error screen 1`] = `
</div>
</div>
</div>
<div
class="MuiPaper-root MuiPaper-elevation MuiPaper-rounded MuiPaper-elevation0 MuiCard-root css-1w4z6qv-MuiPaper-root-MuiCard-root"
style="--Paper-shadow: none;"
>
<div
class="MuiCardContent-root cardContent css-1lt5qva-MuiCardContent-root"
>
<form>
<h6
class="MuiTypography-root MuiTypography-h6 css-v1yov6-MuiTypography-root"
>
Add a note
</h6>
<div
class="MuiFormControl-root MuiFormControl-marginNormal MuiFormControl-fullWidth MuiTextField-root css-16bevx5-MuiFormControl-root-MuiTextField-root"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeMedium MuiInputLabel-outlined MuiFormLabel-colorPrimary MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-sizeMedium MuiInputLabel-outlined css-ll8nw6-MuiFormLabel-root-MuiInputLabel-root"
data-shrink="false"
for=":r6:"
id=":r6:-label"
>
Transaction description
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-colorPrimary MuiInputBase-fullWidth MuiInputBase-formControl css-1pfp7cx-MuiInputBase-root-MuiOutlinedInput-root"
>
<input
aria-invalid="false"
class="MuiInputBase-input MuiOutlinedInput-input css-1eoo0u4-MuiInputBase-input-MuiOutlinedInput-input"
id=":r6:"
name="note"
type="text"
/>
<fieldset
aria-hidden="true"
class="MuiOutlinedInput-notchedOutline css-1focg1t-MuiOutlinedInput-notchedOutline"
>
<legend
class="css-81qg8w"
>
<span>
Transaction description
</span>
</legend>
</fieldset>
</div>
</div>
<div
class="MuiStack-root css-mw1pf-MuiStack-root"
>
<span
class="MuiTypography-root MuiTypography-caption css-cmorml-MuiTypography-root"
>
Attention: transaction notes are public
</span>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-outlined MuiButton-outlinedPrimary MuiButton-sizeMedium MuiButton-outlinedSizeMedium MuiButton-colorPrimary MuiButton-root MuiButton-outlined MuiButton-outlinedPrimary MuiButton-sizeMedium MuiButton-outlinedSizeMedium MuiButton-colorPrimary css-vi2kll-MuiButtonBase-root-MuiButton-root"
tabindex="0"
type="submit"
>
Add note
</button>
</div>
</form>
</div>
</div>
<div
class="MuiPaper-root MuiPaper-elevation MuiPaper-rounded MuiPaper-elevation0 MuiCard-root css-1w4z6qv-MuiPaper-root-MuiCard-root"
style="--Paper-shadow: none;"
Expand Down
Loading