-
Notifications
You must be signed in to change notification settings - Fork 474
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'epic-tx-flow' into recommended-nonce
- Loading branch information
Showing
25 changed files
with
947 additions
and
375 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { | ||
createContext, | ||
type Dispatch, | ||
type ReactElement, | ||
type ReactNode, | ||
type SetStateAction, | ||
useState, | ||
type ComponentProps, | ||
useEffect, | ||
} from 'react' | ||
import NewModalDialog from '@/components/common/NewModalDialog' | ||
import { ReplaceTxMenu, NewTxMenu, RejectTx, TokenTransferFlow } from '@/components/TxFlow' | ||
import { useRouter } from 'next/router' | ||
|
||
export enum ModalType { | ||
SendTokens = 'sendTokens', | ||
RejectTx = 'rejectTx', | ||
ReplaceTx = 'replaceTx', | ||
NewTx = 'newTx', | ||
} | ||
|
||
const ModalTypes = { | ||
[ModalType.SendTokens]: TokenTransferFlow, | ||
[ModalType.RejectTx]: RejectTx, | ||
[ModalType.ReplaceTx]: ReplaceTxMenu, | ||
[ModalType.NewTx]: NewTxMenu, | ||
} | ||
|
||
type VisibleModalState<T extends ModalType> = { | ||
type: T | ||
props: ComponentProps<typeof ModalTypes[T]> | ||
} | ||
|
||
type ContextProps<T extends ModalType> = { | ||
visibleModal: VisibleModalState<T> | undefined | ||
setVisibleModal: Dispatch<SetStateAction<VisibleModalState<T> | undefined>> | ||
} | ||
|
||
export const ModalContext = createContext<ContextProps<ModalType>>({ | ||
visibleModal: undefined, | ||
setVisibleModal: () => {}, | ||
}) | ||
|
||
export const ModalProvider = ({ children }: { children: ReactNode }): ReactElement => { | ||
const [visibleModal, setVisibleModal] = useState<VisibleModalState<ModalType>>() | ||
const router = useRouter() | ||
|
||
const Component = visibleModal ? ModalTypes[visibleModal.type] : null | ||
const props = visibleModal ? visibleModal.props : {} | ||
|
||
// Close the modal if user navigates | ||
useEffect(() => { | ||
router.events.on('routeChangeComplete', () => { | ||
setVisibleModal(undefined) | ||
}) | ||
}, [router]) | ||
|
||
return ( | ||
<ModalContext.Provider value={{ visibleModal, setVisibleModal }}> | ||
{children} | ||
<NewModalDialog open={!!visibleModal}> | ||
{/* @ts-ignore TODO: Fix this somehow */} | ||
{visibleModal && <Component {...props} />} | ||
</NewModalDialog> | ||
</ModalContext.Provider> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import TxButton, { SendNFTsButton, SendTokensButton } from '@/components/TxFlow/common/TxButton' | ||
import { useTxBuilderApp } from '@/hooks/safe-apps/useTxBuilderApp' | ||
import { Box, Typography } from '@mui/material' | ||
import { ModalContext, ModalType } from '@/components/TxFlow/ModalProvider' | ||
import { useContext } from 'react' | ||
|
||
const BUTTONS_HEIGHT = '91px' | ||
|
||
const NewTxMenu = () => { | ||
const { setVisibleModal } = useContext(ModalContext) | ||
const txBuilder = useTxBuilderApp() | ||
|
||
return ( | ||
<Box display="flex" flexDirection="column" alignItems="center" gap={2} width={452} m="auto"> | ||
<Typography variant="h6" fontWeight={700}> | ||
New transaction | ||
</Typography> | ||
<SendTokensButton | ||
onClick={() => setVisibleModal({ type: ModalType.SendTokens, props: {} })} | ||
sx={{ height: BUTTONS_HEIGHT }} | ||
/> | ||
|
||
<SendNFTsButton onClick={() => console.log('open send NFTs flow')} sx={{ height: BUTTONS_HEIGHT }} /> | ||
|
||
{txBuilder && txBuilder.app && ( | ||
<TxButton | ||
startIcon={<img src={txBuilder.app.iconUrl} height={20} width="auto" alt={txBuilder.app.name} />} | ||
variant="outlined" | ||
onClick={() => console.log('open contract interaction flow')} | ||
sx={{ height: BUTTONS_HEIGHT }} | ||
> | ||
Contract interaction | ||
</TxButton> | ||
)} | ||
</Box> | ||
) | ||
} | ||
|
||
export default NewTxMenu |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import type { ReactElement } from 'react' | ||
import { Typography } from '@mui/material' | ||
import type { SafeTransaction } from '@safe-global/safe-core-sdk-types' | ||
|
||
import useAsync from '@/hooks/useAsync' | ||
import SignOrExecuteForm from '@/components/tx/SignOrExecuteForm' | ||
import { createRejectTx } from '@/services/tx/tx-sender' | ||
import TxLayout from '@/components/TxFlow/common/TxLayout' | ||
|
||
type RejectTxProps = { | ||
txNonce: number | ||
} | ||
|
||
const RejectTx = ({ txNonce }: RejectTxProps): ReactElement => { | ||
const [rejectTx, rejectError] = useAsync<SafeTransaction>(() => { | ||
return createRejectTx(txNonce) | ||
}, [txNonce]) | ||
|
||
return ( | ||
<TxLayout title="Reject transaction"> | ||
<SignOrExecuteForm safeTx={rejectTx} isRejection onSubmit={() => {}} error={rejectError}> | ||
<Typography mb={2}> | ||
To reject the transaction, a separate rejection transaction will be created to replace the original one. | ||
</Typography> | ||
|
||
<Typography mb={2}> | ||
Transaction nonce: <b>{txNonce}</b> | ||
</Typography> | ||
|
||
<Typography mb={2}> | ||
You will need to confirm the rejection transaction with your currently connected wallet. | ||
</Typography> | ||
</SignOrExecuteForm> | ||
</TxLayout> | ||
) | ||
} | ||
|
||
export default RejectTx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import { Button, Container, Grid, Paper, Step, StepLabel, Stepper, SvgIcon, Tooltip, Typography } from '@mui/material' | ||
|
||
import InfoIcon from '@/public/images/notifications/info.svg' | ||
import RocketIcon from '@/public/images/transactions/rocket.svg' | ||
import CheckIcon from '@mui/icons-material/Check' | ||
import DeleteIcon from '@/public/images/common/delete.svg' | ||
import { SendTokensButton } from '@/components/TxFlow/common/TxButton' | ||
import { useQueuedTxByNonce } from '@/hooks/useTxQueue' | ||
import { isCustomTxInfo } from '@/utils/transaction-guards' | ||
|
||
import css from './styles.module.css' | ||
import { useContext } from 'react' | ||
import { ModalContext, ModalType } from '@/components/TxFlow/ModalProvider' | ||
|
||
const wrapIcon = (icon: React.ReactNode) => <div className={css.circle}>{icon}</div> | ||
|
||
const steps = [ | ||
{ | ||
label: 'Create new transaction with same nonce', | ||
icon: <div className={css.redCircle} />, | ||
}, | ||
{ | ||
label: 'Collect confirmations from owners', | ||
icon: wrapIcon(<CheckIcon fontSize="small" color="border" />), | ||
}, | ||
{ | ||
label: 'Execute replacement transaction', | ||
icon: wrapIcon(<SvgIcon component={RocketIcon} inheritViewBox fontSize="small" color="border" />), | ||
}, | ||
{ | ||
label: 'Initial transaction is replaced', | ||
icon: wrapIcon(<SvgIcon component={DeleteIcon} inheritViewBox fontSize="small" color="border" />), | ||
}, | ||
] | ||
|
||
const btnWidth = { | ||
width: { | ||
xs: 240, | ||
sm: '100%', | ||
}, | ||
} | ||
|
||
const ReplaceTxMenu = ({ txNonce }: { txNonce: number }) => { | ||
const { setVisibleModal } = useContext(ModalContext) | ||
const queuedTxsByNonce = useQueuedTxByNonce(txNonce) | ||
const canCancel = !queuedTxsByNonce?.some( | ||
(item) => isCustomTxInfo(item.transaction.txInfo) && item.transaction.txInfo.isCancellation, | ||
) | ||
|
||
return ( | ||
<Container> | ||
<Grid container justifyContent="center"> | ||
<Grid item component={Paper} xs={8}> | ||
<div className={css.container}> | ||
<Typography variant="h5" mb={1} textAlign="center"> | ||
Need to replace or discard this transaction? | ||
</Typography> | ||
<Typography variant="body1" textAlign="center"> | ||
A signed transaction cannot be removed but it can be replaced with a new transaction with the same nonce. | ||
</Typography> | ||
<Stepper alternativeLabel className={css.stepper}> | ||
{steps.map(({ label }) => ( | ||
<Step key={label}> | ||
<StepLabel StepIconComponent={({ icon }) => steps[Number(icon) - 1].icon}> | ||
<Typography variant="body1" fontWeight={700}> | ||
{label} | ||
</Typography> | ||
</StepLabel> | ||
</Step> | ||
))} | ||
</Stepper> | ||
</div> | ||
<div className={css.container}> | ||
<Grid container alignItems="center" justifyContent="center" flexDirection="row"> | ||
<Grid item xs={12}> | ||
<Typography variant="body2" textAlign="center" fontWeight={700} mb={3}> | ||
Select how you would like to replace this transaction | ||
</Typography> | ||
</Grid> | ||
<Grid item container justifyContent="center" alignItems="center" gap={1} xs={12} sm flexDirection="row"> | ||
<SendTokensButton | ||
onClick={() => setVisibleModal({ type: ModalType.SendTokens, props: { txNonce } })} | ||
sx={btnWidth} | ||
/> | ||
</Grid> | ||
<Grid item> | ||
<Typography variant="body2" className={css.or}> | ||
or | ||
</Typography> | ||
</Grid> | ||
<Grid | ||
item | ||
container | ||
xs={12} | ||
sm | ||
justifyContent={{ | ||
xs: 'center', | ||
sm: 'flex-start', | ||
}} | ||
alignItems="center" | ||
textAlign="center" | ||
flexDirection="row" | ||
> | ||
<Tooltip | ||
arrow | ||
placement="top" | ||
title={canCancel ? '' : `Transaction with nonce ${txNonce} already has a reject transaction`} | ||
> | ||
<span style={{ width: '100%' }}> | ||
<Button | ||
onClick={() => setVisibleModal({ type: ModalType.RejectTx, props: { txNonce } })} | ||
variant="outlined" | ||
fullWidth | ||
sx={{ mb: 1, ...btnWidth }} | ||
disabled={!canCancel} | ||
> | ||
Reject transaction | ||
</Button> | ||
</span> | ||
</Tooltip> | ||
|
||
<div> | ||
<Typography variant="caption" display="flex" alignItems="center"> | ||
How does it work?{' '} | ||
<Tooltip | ||
title={`An on-chain rejection doesn't send any funds. Executing an on-chain rejection will replace all currently awaiting transactions with nonce ${txNonce}.`} | ||
arrow | ||
> | ||
<span> | ||
<SvgIcon | ||
component={InfoIcon} | ||
inheritViewBox | ||
fontSize="small" | ||
color="border" | ||
sx={{ verticalAlign: 'middle', ml: 0.5 }} | ||
/> | ||
</span> | ||
</Tooltip> | ||
</Typography> | ||
</div> | ||
</Grid> | ||
</Grid> | ||
</div> | ||
</Grid> | ||
</Grid> | ||
</Container> | ||
) | ||
} | ||
|
||
export default ReplaceTxMenu |
File renamed without changes.
Oops, something went wrong.